Java SE进阶笔记


零、IDEA集成开发环境

0.1 IDEA的使用

  • 在IDEA中,选择 File-New-Project 创建一个工程
  • 工程命名并为其选择工程目录,点击Finish
  • 弹出项目结构,点击上面的+号,选择New Module
  • 选择Java,点击Next,为Module命名,点击Finish,点击OK
  • 模块创建完成,在模块中的src中新建Java类

0.2 快捷键

快速生成main方法 psvm或main
快速生成输出语句 sout
删除一行 Ctrl+Y
左侧列表移动开合 方向键
新增、新建、添加的快捷键 Alt+Insert
窗口变大小 Ctrl+Shift+F12
切窗口 Alt+左右箭头
运行 Ctrl+Shift+F10
单行注释 Ctrl+/
多行注释 Ctrl+Shift+/
定位方法属性变量 停到单词下单击Ctrl
纠正错误 Alt+Enter
查看类的属性和方法 Ctrl+F12

一、面向对象

1.1 final关键字

  • final修饰的变量不能被修改
  • final修饰的变量必须显式初始化
  • final修饰的方法不能被覆盖
  • final修饰的类不能被继承
  • 如果修饰的引用,那这个引用只能指向一个对象,不可重新赋值,但是被指向的对象可以修改
  • 构造方法不能被final修饰

1.1.1 final修饰的(基本数据类型)变量不能被修改

实例:

public class Test {
    public static void main(String[] args) {
        final int k = 10;
        k = 20;
    }
}

运行结果:

java: 无法为最终变量k分配值

1.1.2 final修饰的实例变量

  • 被final修饰的实例变量必须手动赋值,不会自动赋默认值
  • 在构造方法里赋值也行,setter方法不可行

实例:

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

    }
}
class Person{
    final int age = 0;//手动赋值
    final boolean sex;//被final修饰的实例变量必须手动赋值,系统不会自动赋默认值
    public Person(){
		this.sex = true;//手动赋值
	}
    public Person(boolean b){
		this.sex = b;//手动赋值
	}
}
  • 实例变量被final关键字修饰后,一般会加static变为静态
  • static final修饰的变量称为常量,常量不可更改,一般为公开的

实例:

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

    }
}
class Chinese{
    String idCard;
    String name;
    static final String COUNTRY = "中国";
}

1.1.3 final修饰的引用变量不能被修改

  • final修饰的引用变量不能被修改,但引用内部的数据是可以被修改的

实例:

public class Test {
    public static void main(String[] args) {
        final Person p = new Person(30);//该引用只能指向一个对象,并且永远只指向这个对象
        p = new Person(30);//p引用保存的地址是不可修改的
        System.out.println(p.age);
        p.age = 40;//引用内部的数据是可以被修改的
        System.out.println(p.age);
    }
}
class Person{
    int age;

    public Person() {
    }
    public Person(int age) {
        this.age = age;
    }
}

运行结果:

java: 无法为最终变量p分配值

1.1.4 final关键字修饰的类不能被继承

实例:

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

    }
}
final class A{

}
class B extends A{

}

运行结果:

java: 无法从最终A进行继承

1.1.5 final修饰的方法不能被覆盖重写

实例:

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

    }
}
class A{
    public final void doSome(){

    }
}
class B extends A{
    public  void doSome(){

    }
}

运行结果:

java: B中的doSome()无法覆盖A中的doSome()
  被覆盖的方法为final

1.2 抽象类

抽象类是类和类之间有共同特征再抽象成抽象类

  • 在Java中采用abstract定义的类就是抽象类,采用abstract定义的方法就叫抽象方法
  • 抽象类属于引用数据类型
  • 抽象方法只能存在于抽象类中
  • 抽象父类中有抽象方法,继承的子类要么也为抽象类,要么在子类中覆盖抽象方法
  • 抽象类无法实例化,无法创建对象
  • 抽象类也有构造方法,供子类使用
  • 抽象类不能用final修饰,抽象类就是用来被继承的
  • 抽象方法不能被final修饰,抽象方法就是用来被子类实现的
  • 向上转型多态编译时,会绑定父类中的方法,在执行时会执行子类的。

1.2.1 采用abstract定义抽象类

抽象类定义的语法格式:

[修饰符列表] abstract class 类名{
	类体;
}

1.2.2 抽象方法

抽象方法是没有实现、没有方法体的方法

例如:

public abstract void doSome();

实例:

public class Test {
    public static void main(String[] args) {
        A a = new A();//报错,抽象类无法实例化
        B b = new B();//非抽象子类可以实例化
    }
}
abstract class A{
    public abstract void doSome();
    public void doOther(){//抽象类中可以存在非抽象方法

    }
}
class B extends A{
    public void doSome(){//抽象方法在非抽象子类中必须重写

    }
}
abstract class C extends A{//在抽象子类中不需要重写父类抽象方法
}

1.3 接口

1.3.1 接口的基础语法

  • 接口也是一种引用数据类型,接口编译后也是.class字节码文件
  • 接口是完全抽象的(抽象类是半抽象)
  • 接口是可以继承的,并且支持多继承
  • 接口中只能包含常量和抽象方法(没有构造方法)
  • 接口中所有元素都是公开的,public可以省略
  • 接口中抽象方法默认都是public abstract,可以省略
  • 接口中变量都是public static final,可以省略,必须显式初始化
  • 一个非抽象的类,实现接口时,必须将接口中所有方法实现
  • 一个类可以实现多个接口
  • extends和implements同时出现时,必须extends写在前,implements写在后
  • 使用接口时,可以使用多态(父类型引用指向子类型对象)

定义接口的语法格式:

[修饰符列表] interface 接口名{

}

实例:

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

    }
}
interface A{

}
interface B extends A{

}
interface C extends A,B{

}
interface MyMath{

    //public static final double PI=3.12;
    double PI=3.12;
    //public abstract int sum(int a,int b);
    int sum(int a,int b);
}
  • 接口可以看作是类,类之间叫继承,类和接口之间叫实现通过implements关键字完成
  • 当一个非抽象的类实现接口时,必须将接口中的所有抽象方法全部实现(覆盖重写)
  • 子类的方法访问权限要更低(封闭)才行

实例:

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

    }
}

interface MyMath{
    double PI=3.12;
    int sum(int a,int b);
    int sub(int a,int b);
}
class MyMathImp implements MyMath{//重写的方法访问权限必须更封闭
	public int sum(int a,int b){
	return a+b;
	}    
	public int sub(int a,int b){
		return a-b;
	}    
}
//abstract class MyMathImp implements MyMath{

//}

接口和多态联合使用

实例:

public class Test {
    public static void main(String[] args) {
        MyMath mm = new MyMathImp();
        System.out.println(mm.sum(30,10));
        System.out.println(mm.sub(30,10));
    }
}

interface MyMath{
    double PI=3.14;
    int sum(int a,int b);
    int sub(int a,int b);
}
//class MyMathImp implements MyMath{
//
//}
class MyMathImp implements MyMath{
    public int sum(int a,int b){
        return a+b;
    }
    public int sub(int a,int b){
        return a-b;
    }
}

运行结果:

40
20
  • 一个类可以实现多个接口,弥补了类不能多继承的缺点

  • 接口之间在强制类型转换时,没有继承关系也可以强转。但是运行时可能会出现ClassCastException异常(假装当没讲,容易混淆没啥用)

实例:

public class Test {
    public static void main(String[] args) {
		A a = new D();
		B b = new D();
		C c = new D();
		
		B b2 = (B)a;//A和B之间没有继承关系
		b2.b();
    }
}

interface A{
    void a();
}
interface B{
    void b();
}
interface C{
    void c();
}
class D implements A,B,C{
    public void a(){

    }
    public void b(){
		System.out.println("b....");
    }
    public void c(){
 
    }
}

运行结果:

b....

实例:

public class Test {
    public static void main(String[] args) {
        B b = new C();
       if(B instanceof A){
        	A a = (A)b;//如果没有检查,就会报错
	/*Exception in thread "main" java.lang.ClassCastException: class C cannot be cast to class A (C and A are in unnamed module of loader 'app')
	at Test1.main(Test1.java:4)*/
       }
       
    }
}

interface A{

}
interface B{

}
class C implements B{

}
  • extends和implements同时出现,extends写在前,implements写在后
  • 接口通常提取的是行为动作

实例:

public class Test {
    public static void main(String[] args) {
       Flyable f = new Cat();
       f.fly();
    }
}
class Animal{

}
interface Flyable{
    void fly();
}
class Cat extends Animal  implements Flyable{
    public void fly(){
        System.out.println("飞猫起飞!");
    }
}

运行结果:

飞猫起飞!

1.3.2 接口在开发中的作用

接口在开发中的作用,类似多态在开发中的作用

实例:

public interface FoodMenu {
    void xiHongShijiDan();
    void yuXiangRouSi();
}
public class Customer {
    //凡是可以用has a形容的,统一用属性的方式存在
    //顾客有一个菜单
    private FoodMenu foodMenu;

    public Customer() {
    }
    public Customer(FoodMenu foodMenu) {
        this.foodMenu = foodMenu;
    }

    public FoodMenu getFoodMenu() {
        return foodMenu;
    }

    public void setFoodMenu(FoodMenu foodMenu) {
        this.foodMenu = foodMenu;
    }
    public void order(){
        //FoodMenu fm = this.getFoodMenu();
        foodMenu.xiHongShijiDan();
        foodMenu.yuXiangRouSi();

    }
}
public class ChinaCooker implements FoodMenu{

    public void xiHongShijiDan() {
        System.out.println("西红柿鸡蛋中国味道");
    }
    public void yuXiangRouSi() {
        System.out.println("鱼香肉丝中国味道");
    }
}
public class AmericaCooker implements FoodMenu{
    public void xiHongShijiDan() {
        System.out.println("西红柿鸡蛋美国味道");
    }
    public void yuXiangRouSi() {
        System.out.println("西红柿鸡蛋美国味道");
    }
}
public class Restaurant {
    public static void main(String[] args) {
        FoodMenu cooker1 = new ChinaCooker();
        Customer c1 = new Customer(cooker1);
        c1.order();
    }

}

运行结果:

西红柿鸡蛋中国味道
鱼香肉丝中国味道

1.3.3 类和类之间的关系

is a、has a、 like a

  • is a:Cat is a Animal 表示继承关系
    凡是满足is a的表示继承关系
  • has a:I has a Pen表示关联关系
    关联关系通常以属性形式存在
  • like a:Cooker like a FoodMenu表示实现关系
    实现关系通常是类实现接口

1.3.4 抽象类和接口的区别

  • 抽象类是半抽象的,接口是完全抽象的
  • 抽象类有构造方法,接口没有构造方法
  • 接口之间支持多继承,类之间只有单继承
  • 一个类可以实现多接口,一个抽象类只能继承一个类
  • 接口中只能出现常量和抽象方法
  • 一般接口使用的比较多,接口一般是对行为的抽象

1.4 包机制

  • package是Java包机制。是为了方便程序的管理,不同功能的类分别存放在不同的包下
  • package是一个关键字,后面加包名。例如:package com.baidu.javase.chapter17
  • package只允许出现在Java源代码的第一行(注释行不算)
  • 包名一般采用公司域名倒序的方式
    • 包名命名规范:公司域名倒序+项目名+模块名+功能名

package的Java程序编译和运行:

//在chapter17目录下的HelloWorld.java文件
package com.baidu.javase.chapter17;
//此时类名已变为com.baidu.javase.chapter17.HelloWorld
public static void main(String[] args){
    System.out.println("Hello World!");
}

在CMD中编译:

javac -d . HelloWorld.java

javac负责编译
-d带包编译
. 代表编译之后生成的东西放在当前目录下
HelloWorld.java 被编译的java文件名

在CMD中运行:

java com.baidu.javase.chapter17.HelloWorld

1.4.1 使用包机制

  • 如果两个程序在同一package下,包名可以省略。不在同一包名下,必须写全包名

实例:

package com.baidu.javase.chapter17;
public class Test1{
	public static void main(String[] args){
		com.baidu.javase.chapter17.HelloWorld hw = new com.baidu.javase.chapter17.HelloWorld();
		HelloWorld hw2 = new HelloWorld();//同一包名下,可以省略包名
	}
}
package com;
public class Test2{
	public static void main(String[] args){
		//Test1在com.baidu.javase.chapter17包下,Test2在com包下,不能省略包名
		com.baidu.javase.chapter17.HelloWorld hw = new com.baidu.javase.chapter17.HelloWorld();
		
	}
}

1.4.2 使用import机制

  • import语句只能出现在package语句之下,class声明语句之上
  • 当两个类不在同一包时,使用import。在同一包内,不需要使用
  • lang包下的东西都不需要手动导,程序自动导

实例:

package com;
import com.baidu.javase.chapter17.HelloWorld;//将需要的类导入
public class Test3{
	public static void mian(String[] args){
		HelloWorld hw = new HelloWorld();
	}
}
package com.baidu.javase.chapter17;

public class Test4{
	public static void mian(String[] args){
		java.util.Scanner s = new java.util.Scanner(System.in);//必须写明包
	}
}
package com.baidu.javase.chapter17;
import java.util.Scanner;
public class Test5{
	public static void mian(String[] args){
		//此类和Scanner类不在同一个包
		java.util.Scanner s = new java.util.Scanner(System.in);//必须写明包
	}
}

在Test5中也可以写为:

import java.util.*;//*在此只能代表某些类的名字

此种写法运行速度不会比以上写法慢

1.5访问权限控制符

  • private 私有
  • protected 受保护
  • public 公开

1.5.1 访问控制修饰符的控制范围

  • private 只能在本类中访问
  • protected 只能在本类,同包,子类中访问
  • public 在任何位置都能访问
  • 默认 只能在本类和同包下访问
    范围由大到小:public > protected > 默认 > private

1.5.2 访问控制修饰符修饰的对象

  • 属性(4个都能用)
  • 方法(4个都能用)
  • 类(public和默认可以)
  • 接口(public和默认可以)

1.6 Object类

Object类中的常用方法可以查阅Java类库的帮助文档

  • API是应用程序编程接口
  • 整个JDK类库就是一个Java SE的API
  • 每一个API都会配置一个API帮助文档

目前需要掌握的Object的方法:

protected Object clone()//负责对象克隆
int hashCode()//获取对象哈希值的一个方法
boolean equals(Object obj)//判断两个对象是否相等
String toString()//将对象转换成字符串形式
protected void finalize()//垃圾回收器负责调用的方法

1.6.1 Object类的toString方法

  • toString方法可以将一个Java对象转换成字符串表示形式

实例:

public class test {
    public static void main(String[] args) {
        MyTime t1 = new MyTime(1970,1,1);
        String s1 = t1.toString();
        System.out.println(s1);
    }
}
class MyTime{
    int year;
    int month;
    int day;
    public MyTime(){

    }
    public MyTime(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

运行结果:

MyTime@49e4cb85

但是我们并不期待这样的结果,我们期待的是具体的日期结果,所以最好重写toString()方法。

  • 建议所有子类都重写toString()方法

实例:

public class test {
    public static void main(String[] args) {
        MyTime t1 = new MyTime(1970,1,1);
        String s1 = t1.toString();
        System.out.println(s1);
    }
}
class MyTime{
    int year;
    int month;
    int day;
    public MyTime(){

    }
    public MyTime(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
   public String toString(){
        return this.year + "年" + this.month + "月" + this.day + "日";
   }
}

运行结果:

1970年1月1日

1.6.2 Object类的equals方法

  • equals方法用来判断两个对象是否相等
  • “==”用来判断两个基本数据类型是否相等,equals用来判断两个引用数据类型是否相等
  • String类重写了equals方法
  • String类重写了toString方法

equals方法的源代码:

public boolean equals(Object obj){
	return (this == obj);//源代码中用的“==”
}

实例:

public class test  {
    public static void main(String[] args) {
        MyTime t1 = new MyTime(1970,1,1);
        MyTime t2 = new MyTime(1970,1,1);
        //判断两个Java对象不能使用“==”
        System.out.println(t1 == t2);//此处判断的是t1、t2中保存的对象内存地址是否相等
    }
}
class MyTime{
    int year;
    int month;
    int day;
    public MyTime(){

    }
    public MyTime(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
}

运行结果:

false

重写equals方法

实例:

public class test {
    public static void main(String[] args) {
        MyTime t1 = new MyTime(1970,1,1);
        MyTime t2 = new MyTime(1970,1,1);

        System.out.println(t1==t2);
        System.out.println(t1.equals(t2));
    }
}
class MyTime{
    int year;
    int month;
    int day;
    public MyTime(){

    }
    public MyTime(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public boolean equals(Object obj) {
        if(obj == null){
			return false;
		}
        //当年月日都相等时,认为两个对象相等
        int year1 = this.year;
        int month1 = this.month;
        int day1 = this.day;
		//想要访问子类中特有的东西
		if(obj instance MyTime){
			return false;
		}
        if(this == obj){//如果地址都相同就不用比下面了
            return true;
        }
       //向下转型 强转
            MyTime mt = (MyTime) obj;//参数传过来的是Object类型:Object obj = new MyTime(1970,1,1);Object类中没有相应的属性
            int year2 = mt.year;
            int month2 = mt.month;
            int day2 = mt.day;
            if(this.year == year2&&this.month == month2&&this.day == day2){
                return true;
            }
            return false;
       
    }
}

运行结果:

false
true

比较两个字符串必须用equals方法

实例:

public class test {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = new String("Hello");
        String s4 = new String("Hello");
        System.out.println(s1==s2);
        System.out.println(s1.equals(s2));
        System.out.println(s3==s4);
        System.out.println(s3.equals(s4));
    }
}

运行结果:

true
true
false
true

String类重写了toString()方法

实例:

public class test {
    public static void main(String[] args) {
        String x = new String("Hello");
        System.out.println(x);//引用数据类型当不写toString方法时,自动调用toString方法
        System.out.println(x.toString());//如果没重写toString,结果应为String@十六进制的地址
    }
}

运行结果:

Hello
Hello

实例:

public class test {
    public static void main(String[] args) {
        Student s1 = new Student(111,"北京");
        Student s2 = new Student(112,"北京");
        System.out.println(s1 == s2);//false
        System.out.println(s1.equals(s2));//true

//        Student s3 = new Student(111,new String("北京"));
//        Student s4 = new Student(112,new String("北京"));//两个北京的内存地址不同,如果用equals比较那两者通用
//        System.out.println(s3 == s4);//false
//        System.out.println(s3.equals(s4));//false
    }
}
class Student{
    int no;
    String school;
    public Student() {
    }

    public Student(int no, String school) {
        this.no = no;
        this.school = school;
    }

    public String toString() {
        return "学号"+no+"学校名称"+school;
    }
    public boolean equals(Object obj) {
        if(obj == null||!(obj instanceof Student)) return false;
        if(this == obj) return true;
        Student s = (Student) obj;
        return this.no == s.no && this.school.equals(s.school);
        //return this.no == s.no && this.school == s.school);//不可以
    }
}

运行结果:

false
false

equals方法的深层次理解

重写equals方法要彻底

实例:

public class test {
    public static void main(String[] args) {
        Address addr1 = new Address("北京","海淀区","111");
        User u1 = new User("张三",addr1);
        // User u1 = new User("张三", new Address("北京","海淀区","111"));
        User u2 = new User("张三", new Address("北京","海淀区","111"));
        System.out.println(u1.equals(u2));//true
    }
}
class User{
    String name;
    Address addr;
    public User() {
    }
    public User(String name, Address addr) {
        this.name = name;
        this.addr = addr;
    }
    //重写equals方法
    //当一个用户的姓名和地址都相同时,表示是同一个用户。这里判断两个User对象是否相等
    public boolean equals(Object obj) {
       if(obj == null||!(obj instanceof User)) return false;
       if(this == obj) return true;
        User u = (User)obj;
       if(this.name.equals(u.name) && this.addr.equals(u.addr)){
            return true;
       }
       return false;
    }
}
class Address{
    String city;
    String street;
    String zipcode;
    public Address() {
    }
    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
    //这里不重写equals方法会导致上面的条件里addr的equals比较的是内存地址
    public boolean equals(Object obj) {
        if(obj == null||!(obj instanceof Address)) return false;
        if(this == obj) return true;
        Address a = (Address)obj;
        if(this.city.equals(a.city) && this.street.equals(a.street) && this.zipcode.equals(a.zipcode)){
            return true;
        }
        return false;
    }
}

运行结果:

true

1.6.3 Object类的finalize方法(了解即可,已过时)

  • JDK9之后已删除此方法

在Object类中的源代码:

protected void finalize() throws Throwable{}

只有一个方法体

  • 当一个对象即将被垃圾回收器回收时,会自动调用finalize()
  • finalize是一个垃圾销毁时机,如果希望在对象即将被销毁时执行一段代码,这段代码写在finalize方法中

实例:

public class test {
    public static void main(String[] args) {
     for (int i=0;i<1000000;i++){
         Person p = new Person();
         //将p变成垃圾
         p = null;
     }
    }
}
class Person{
    //重写finalize方法
    //Person类的对象被垃圾回收器回收时,垃圾回收器负责调用p.finalize();
    protected void finalize() throws Throwable {
        System.out.println("即将被销毁");
    }
}
  • 垃圾回收器不是轻易启动的,垃圾太少或者时机未到等条件下可能启动可能不启动
system.gc();//建议gc启动,可能听,也可能不听,仅仅是概率大一些

1.6.4 Object类的hashCode方法

hashCode的源代码:

public native int hashCode();
  • hashCode()方法带有native关键字,底层调用了C++程序
  • hashCode()方法返回的是哈希码:实际上就是一个内存地址,经过哈希算法得到一个值

实例:

public class test {
    public static void main(String[] args) {
        Object o = new Object();
        int hashCode = o.hashCode();
        System.out.println(hashCode);
    }
}

运行结果:

2083562754

1.7 匿名内部类

  • 内部类:在类的内部又定义了一个新的类
  • 内部类分为:静态内部类、实例内部类、局部内部类

实例:

class Test{
    static class Inner1{//静态内部类
        
    }
    class Inner2{//实例内部类

    }
    public void doSome(){
        int i = 100;//局部变量
        class Inner3{//局部内部类
			
		}
    }
    public void doOther(){
		//doSome()中的局部内部类Inner3,在doOther()中不能用
    }
}
  • 匿名内部类是局部内部类的一种,没有类名

未使用匿名内部类

实例:

public class Test {
    public static void main(String[] args) {
        MyMath mm = new MyMath();
        mm.MySum(new ComputerImp(),100,200);
    }
}
interface Computer{
    //抽象方法
    int sum(int a,int b);
}
class ComputerImp implements Computer{//接口的实现类
    public int sum(int a, int b) {
        return a + b;
    }
}
class MyMath{
    public void MySum(Computer c,int a,int b){
        int retValue = c.sum(a,b);
        System.out.println(a+"+"+b+"="+retValue);
    }
}

使用匿名内部类,可以不写接口的实现类

实例:

public class Test {
    public static void main(String[] args) {
        MyMath mm = new MyMath();
        mm.MySum(new Computer(){
            public int sum(int a,int b){
                return a + b;
            }
        },100,200);
    }
}
interface Computer{
    //抽象方法
    int sum(int a,int b);
}
class MyMath{
    public void MySum(Computer c,int a,int b){
        int retValue = c.sum(a,b);
        System.out.println(a+"+"+b+"="+retValue);
    }
}

二、数组

  • Java中数组是一种引用类型,数组的父类是Object类
  • 数组是一个容器,可以同时容纳多个容器(数组是一个数据的集合)
  • 数组中可以存基本数据类型,也可以存引用数据类型
  • 数组是引用数据类型,所以数组对象是在堆内存中
  • 数组中存储的是Java对象的话,实际上存储的是对象的引用(内存地址),不能直接存储Java对象
  • 在Java中,数组一旦创建,长度不可变
  • 数组分为一维数组、二维数组、三维数组、多维数组...
  • 所有数组对象都有length属性(Java自带),用来获取数组中元素的个数
  • Java中要求数组的元素类型统一:int类型只能装int类型元素
  • 数组在内存方面存储时,数组中的元素内存地址是连续的
  • 数组中首元素的内存地址当作整个数组的内存地址
  • 数组中每一个元素都是由下标的,从0开始,最后一个元素的下标是length-1
  • 对数组元素进行存取时,都需要通过下标

数组的数据结构的优缺点:

优点:

  • 每一个元素的内存地址在空间上是连续的
  • 每一个元素类型相同,占用空间大小一样
  • 知道第一个元素的内存地址,知道每一个元素的占用空间,也知道下标,所以可以通过数学表达式计算出某个下标上元素的内存地址,直接通过内存地址定位元素,所以数组的检索效率最高
  • 数组存储100个和一万个元素在元素查询和检索方面效率是相同的,数组在元素查找时不会一个一个的找,而是通过数学表达式直接算出来的

缺点:
- 为了保证数组每个元素内存地址的连续性,所以在数组上随机增删元素的时候,效率较低,因为随即增删元素时会涉及到后面的元素同意向前或者向后位移的操作
- 数据不能存储大数据量,因为很难在内存中找到一块特别大的连续的内存空间
- 对于数组最后一个元素的增删,是没有效率影响的

2.1一维数组的声明和初始化

一维数组的声明语法格式:

数据类型[] array;
int[] i;
String[] str;

初始化数组有两种方法:

//静态初始化语法格式
int[] array = {100,200,300};
//动态初始化语法格式
int[] a = new int[5];//初始化一个长度为5的int类型数组,每个元素为默认值0;
String[] str = new String[6];//初始化长度为6的String类型数组,每个元素为默认值null 

对一维数组元素的访问

实例:

public class test {
    public static void main(String[] args) {
        int[] a = {1,100,10,20,55,689};
        System.out.println("数组中元素个数"+a.length);
        System.out.println("第一个元素"+a[0]);
        System.out.println("最后一个元素"+a[5]);
        System.out.println("最后一个元素"+a[a.length - 1]);
        //修改第一个元素为111;
        a[0] = 111;
        //修改最后一个元素为0;
        a[a.length - 1] = 0;
        System.out.println("第一个元素"+a[0]);
        System.out.println("最后一个元素"+a[a.length - 1]);
    }
}

运行结果:

数组中元素个数6
第一个元素1
最后一个元素689
最后一个元素689
第一个元素111
最后一个元素0

一维数组的遍历:

public class test {
    public static void main(String[] args) {
        int[] a = {1,100,10,20,55,689};
        for (int i=0;i< a.length;i++){//遍历数组
            System.out.println("顺序输出"+a[i]);
        }
        //System.out.println(a[6]);//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 6at test2.main(test2.java:7)
        for (int i = a.length - 1;i >= 0;i--){//反向遍历数组
            System.out.println("倒序输出"+a[i]);
        }
    }
}

运行结果:

顺序输出1
顺序输出100
顺序输出10
顺序输出20
顺序输出55
顺序输出689
倒序输出689
倒序输出55
倒序输出20
倒序输出10
倒序输出100
倒序输出1

动态初始化一维数组

实例:

public class test {
    public static void main(String[] args) {
        int[] a = new int[6];
        for (int i=0;i< a.length;i++){
            System.out.println("第"+i+"个元素:"+a[i]);
        }
    }
}

运行结果:

第0个元素:0
第1个元素:0
第2个元素:0
第3个元素:0
第4个元素:0
第5个元素:0
  • 当创建数组时,不确定数组中存储哪些数据,用动态初始化方式

方法的参数是数组时:

public class test {
    public static void main(String[] args) {
        int[] a = {1,2,3,4,5};
        printArray(a);
        String[] str = {"Hello","World","Huawei","harmony"};
        printArray(str);
        String[] str1 = new String[3];
        printArray(str1);
        printArray(new int[]{1,2,3});//直接传一个静态数组进去,括号里为空
    }
    public static void printArray(int[] array){
        for (int i = 0;i< array.length;i++){
            System.out.println(array[i]);
        }
    }
    public static void printArray(String[] array){
        for (int i = 0;i< array.length;i++){
            System.out.println(array[i]);
        }
    }
}

运行结果:

1
2
3
4
5
Hello
World
Huawei
harmony
null
null
null
1
2
3

2.2 关于main方法的String数组

  • JVM调用main方式时,一定会传一个String数组过来,参数默认为0,而非null
  • 用户可以在控制台输入参数,参数会自动转换成String[] args
    • 例如:在CMD中运行:java 类名 abc def xyz,JVM会自动将abc def xyz用空格分离,放入String args数组中

这有什么用?

实例:

public class Test{
	
	public static void main(String[] args){
		if(args.length != 2){
			System.out.println("请输入用户名和密码");
		return;
		}
		String username = args[0];
		String password = args[1];
	//此种写法可能导致空指针异常
	//"admin".equals(username)更好
	if(username.equals("admin")&&password.equals("123")){
				System.out.println("欢迎回来");
		}else{
			System.out.println("验证失败");
		}
	}
}

2.3 数组中存储的类型为引用数据类型

实例:

public class Test{

    public static void main(String[] args){
        Animal a1 = new Animal();
        Animal a2 = new Animal();
        Animal[] animals = {a1,a2};
        for(int i=0;i

2.4 数组扩容

  • 在Java中,数组长度一旦确定就不可改变
  • 数组扩容的方法是:先建一个大容量的数组,然后将小容量数组中的数据一个一个拷贝到大数组当中
  • 数组扩容效率较低,尽可能的少进行数据拷贝,在创建前预估好数组容量
System.arraycopy();//内置的系统扩容函数,需要传五个参数

//拷贝源
int [] src = {1,11,22,3,4};
//拷贝目标
int [] dest = new int[20];
//调用JDK System类中的arraycopy方法,来完成数组的拷贝
System.arraycopy(src,1,dest,3,2);
//System.arraycopy(拷贝源,源起点,拷贝目标,目标起始下标,拷贝长度);
//System.arraycopy(src,0,dest,0,src.length);全部拷贝完
//遍历目标数组
for(int i = 0;i < dest.length;i++){
    System.out.println(dest[i]);//0 0 0 11 22 0...0
}
  • 数组中存储的元素是引用类型,也可以拷贝
//拷贝源
String [] strs = {"Hello","World","study","java","oracle"};
//拷贝目标
String [] newStrs = new String[20];
//调用JDK System类中的arraycopy方法,来完成数组的拷贝
System.arraycopy(strs,0,newStrs,0,strs.length);

//遍历目标数组
for(int i = 0;i < newStrs.length;i++){
    System.out.println(newStrs[i]);//"Hello","World","study","java","oracle" null...null
}

//在拷贝引用对象时,拷贝的是对象的地址,而不是对象
Object [] obj = {new Object(),new Object()};

2.5 二维数组

  • 二维数组是特殊的一维数组,二维数组相当于一维数组中每个元素都为一个一维数组

  • 三维数组相当于二维数组中每个元素都为一个二维数组,基本用不到三维

  • 二维数组的静态初始化

    int[][] array ={{1,2},{3,4}};
    

2.5.1 二维数组的length属性

int[][] a ={
				{1,2},
				{3,4,5}
			};
System.out.println(a.length);//二维数组中有2个一维数组
System.out.println(a[1].length);//第一行的一维数组有2个元素

2.5.2 二维数组的元素访问

 int[][] a = {
                {34,4,65},
                {100,200,3900,111},
                {0}
        };
		//a[第几个一维数组][第几个一维数组的第几个元素]
        //取出第2个数组中的第一个元素
        System.out.println(a[1][0]);

2.5.3 二维数组的遍历

int[][] a = {
                {34,4,65},
                {100,200,3900,111},
                {0}
        };
		//遍历二维数组
        for(int i = 0;i < a.length;i++){//外层循环三次,负责纵向
            for (int j = 0;j < a[i].length;j++){//内层循环每层一维的长度,负责横向
                System.out.println(a[i][j]);
            }
        }

2.5.4 方法的参数是一个二维数组时

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

        int[][] a = {
                {34,4,65},
                {100,200,3900,111},
                {0}
        };
        printArray(a);
    }
    public static void printArray(int[][] array){//遍历二维数组
        for(int i = 0;i < array.length;i++){
            for (int j = 0;j < array[i].length;j++){
                System.out.println(array[i][j]);
            }
        }
    }
}

2.6 一维数组的内存结构

image-20211115155045760

2.7 数组模拟栈数据结构

要求:

  1. 这个栈可以存储Java中任何引用类型的数据
  2. 在栈中提供push方法模拟压栈
  3. 在栈中提供pop方法模拟弹栈
  4. 编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作

MyStack.java

public class MyStack {
    private Object[] elements;
    private int index;
    
    public MyStack() {
        this.elements = new Object[10];//默认栈的初始化容量为10
        this.index = -1;//index为-1表示栈帧指向顶部元素,为0表示指向顶部元素的上方
    }
    //压栈方法
    public void push(Object obj){
        if(this.index >= this.elements.length - 1){
            System.out.println("压栈失败,栈已满!");
            return;
        }
        //走到此处说明栈没满
        //向栈中加一个元素,栈帧向上移动一个位置
        this.index++;
        this.elements[index] = obj;
        //在sout执行时,如果输出引用的话,自动调用引用的toString()方法
        System.out.println("压栈 " + obj + " 成功!栈帧指向:" + this.index);
    }
    //弹栈方法
    public void pop(){
        if(this.index < 0){
            System.out.println("弹栈失败,栈已空!");
            return;
        }
        //栈没空
        System.out.print("弹栈 " + this.elements[index] + "成功!");
        index--;
        System.out.println("栈帧指向:" + this.index);
    }
    public int getIndex() {
        return index;
    }
    public void setIndex(int index) {
        this.index = index;
    }
    public Object[] getElements() {
        return elements;
    }
    public void setElements(Object[] elements) {
        this.elements = elements;
    }
}

StackSimulation.java

public class StackSimulation {
    public static void main(String[] args) {
        MyStack stack = new MyStack();
        stack.push(new Object());
        stack.push(new Object());
        stack.push(new Object());
        stack.push(new Object());
        stack.push(new Object());
        stack.push(new Object());
        stack.push(new Object());
        stack.pop();
        stack.pop();
        stack.pop();
        stack.pop();
    }
}

2.8 酒店管理系统模拟

要求:

  1. 该系统的用户是酒吧前台
  2. 酒店中所有的房间使用一个二维数组来模拟
  3. 酒店中每一个房间应该是一个Java对象
  4. 每一个房间Room应该有:房间编号、房间类型属性、房间是否空闲
  5. 系统对外提供的功能:
    1. 可以预订房间:用户输入房间号订房
    2. 可以退房:用户输入房间编号退房
    3. 可以查看所有房间的状态:用户输入某个指令应该可以查看所有房间状态

Hotel.java

public class Hotel {
    private Room[][] rooms;
    public Hotel(){
        rooms = new Room[3][10];//三层每层十个房
        for(int i = 0;i < rooms.length;i++){//i+1是楼层
            for (int j = 0;j < rooms[i].length;j++){
                if(i == 0){
                    rooms[i][j] = new Room((i+1)*100+j+1,"单人间",true);
                }else if(i == 1){
                    rooms[i][j] = new Room((i+1)*100+j+1,"标准间间",true);
                }else if(i == 2){
                    rooms[i][j] = new Room((i+1)*100+j+1,"总统套房",true);
                }

            }
        }

    }
    public void print(){
        for(int i = 0;i < rooms.length;i++) {//i+1是楼层
            for (int j = 0; j < rooms[i].length; j++) {//负责输出一层
                Room room = rooms[i][j];
                System.out.print(room);
            }
            System.out.println();
        }
    }
    public void order(int roomNo){//订房
        Room room = rooms[roomNo / 100 - 1][roomNo % 100 - 1];
        room.setStatus(false);
        System.out.println(roomNo+"已订房!");
    }
    public void exit(int roomNo){//退房
        Room room = rooms[roomNo / 100 - 1][roomNo % 100 - 1];
        room.setStatus(true);
        System.out.println(roomNo+"已退房!");
    }
}

Room.java

public class Room {
    private int no;

    private String type;

    private boolean status;

    public Room() {

    }

    public Room(int no, String type, boolean status) {
        this.no = no;
        this.type = type;
        this.status = status;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public boolean getStatus() {
        return status;
    }

    public void setStatus(boolean status) {
        this.status = status;
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof Room)) return false;
        if(this == obj) return true;
        Room room = (Room)obj;
        return this.no == room.getNo();
    }

    @Override
    public String toString() {
        return "[" + no + "," + type + "," + (status ? "空闲" : "占用") + "]";
    }
}

HotelSimulation.java

import java.util.Scanner;

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

        Hotel hotel = new Hotel();

        System.out.println("欢迎使用酒店管理系统");
        while (true){
            System.out.println("功能编号对应的功能:");
            System.out.println("[1] 查看房间列表");
            System.out.println("[2] 订房");
            System.out.println("[3] 退房");
            System.out.println("[0] 退出系统");
            Scanner s = new Scanner(System.in);
            System.out.println("请输入功能编号:");
            int i = s.nextInt();
            if(i == 1){
                hotel.print();
            }else if (i == 2){
                System.out.println("请输入订房编号:");
                int roomNo = s.nextInt();
                hotel.order(roomNo);
            }else if (i == 3){
                System.out.println("请输入退房编号:");
                int roomNo = s.nextInt();
                hotel.exit(roomNo);
            }else if (i == 0){
                System.out.println("再见!");
                return;
            }
        }

    }
}

2.9 Arrays工具类

java.util.Arrays

工具类中方法大部分都是静态的

int[] arr ={112,3,4,56,67,1};
//给数组排序
Arrays.sort(arr//从小到大排序
for(int i = 0;i < arr.length;i++){
    System.out.println(arr[i]);//1 3 4 56 67 112
}

2.10 冒泡排序算法

public class BubbleSort {
    public static void main(String[] args) {
        /*冒泡排序法:
            3,2,7,6,8
            第一次循环(最大的交换到右边):
            3和2比较结果:2,3,7,6,8
            3和7比较结果:2,3,7,6,8
            7和6比较结果:2,3,6,7,8
            7和8比较结果:2,3,6,7,8
            第二次循环:(第二大的交换到右边)
            2和3比较结果:2,3,6,7
            3和6比较结果:2,3,6,7
            6和7比较结果:2,3,6,7
            第三次循环:(第三大的交换到右边)
            2和3比较结果:2,3,6
            3和6比较结果:2,3,6
            第四次循环:(第四大的交换到右边)
            2和3比较结果:2,3
        */
        int[] arr = {45,3,6,11,678,224,9,0};

        for(int i = 0;i < arr.length - 1;i++){
            System.out.println("第" + (i+1) + "次循环");
            for(int j = 0;j < arr.length - 1 - i;j++){
                if(arr[j] > arr[j+1]){
                    int temp;
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    System.out.println(arr[j] + "和" + arr[j+1] + "交换位置:" + f(arr));
                }else {
                    System.out.println(arr[j] + "和" + arr[j+1] + "输出结果:" + f(arr));
                }
            }
        }
    }
    public static String f(int[] array){
        String str = "";
        for(int i = 0;i < array.length;i++){
            str = str + array[i] + " ";
        }
        return str;
    }
}

2.11 选择排序算法

public class SelectionSort {
    public static void main(String[] args) {
        //选择排序
        /*
            选择排序比冒泡排序效率高
            选择排序每次交换都是有意义的
            循环一次,然后找出参与比较的数据中的最小值,将这个值和最前数据作比较交换位置

            参与比较的数据:3 1 6 2 5
            第一次循环:
            1 3 6 2 5
            参与比较的数据:3 6 2 5
            第二次循环:
            2 6 3 5
            参与比较的数据:6 3 5
            第三次循环:
            3 6 5
            参与比较的数据:6 5
            第四次循环:
            5 6
        */
        int[] arr = {3,1,6,2,5};
        for (int i =0;i

2.12 二分法查找

public class ArraySearch {
    public static void main(String[] args) {
        /*
        二分法查找,必须按从小到大顺序排序的数组
        10(下标0) 11 12 13 14 15 16 17 18 19 20(下标10)
        通过二分法查找找出18这个元素的下标
        (0+10)/2计算出中间元素下标:5
        拿着中间元素和目标查找元素对比
        15<18  18在中间元素的右侧
        把开始下标元素改为5+1,再重新计算中间元素下标为8,对应中间元素18
        18 = 18表示找到了
         */

        int[] arr = {100,200,230,235,600,1000,2000,9999};
        //找出数组中200所在的下标
        int index = binarySearch0(arr,20);
        System.out.println(index == -1 ? "该元素不存在" : "该元素下标为:"+index);
    }
    public static int binarySearch0(int[] arr,int dest){
        int begin = 0;
        int end = arr.length - 1;
        while(begin <= end){//只要开始在结束前,就继续循环
            int mid = (begin + end)/2;
            if(arr[mid] == dest){
                return mid;
            }else if(arr[mid] < dest){
                //在中间的右边
                begin = mid + 1;
            }else if(arr[mid] > dest){
                //在中间的左边
                end = mid - 1;
            }
        }
        return -1;
    }
}

三、常用类

3.1 String字符串的存储原理

java.lang.String

  1. String表示字符串类型,属于引用类型

  2. Java中使用双引号括起来的都是String对象

  3. Java中规定,双引号括起来的字符串是不可变的,直接存储在方法区中的字符串常量池中

  4. String为什么是不可变的?

    我看过源代码,String了类有一个byte[]数组,这个数组采用final修饰哈哈哈哈,因为数组一旦创建长度就不可变,并且被final修饰的引用一旦指向某个对象之后,不可指向其他对象,所以不可变

  5. StringBuffer和StringBuilder为什么是可变的?

    我看过源代码,他们内部实际上是一个byte[]数组,这个数组没有被final修饰,初始化容量为16,当存满后会进行扩容,底层调用了数组拷贝的方法

3.2 String类的构造方法

public class StringTest {
    public static void main(String[] args) {
        //创建字符串最常用的一种方法
        String str1 = "Hello World";
        /*String的常用的构造方法
        String s = new String("");
        String s = "";
        String s = new String(char数组);
        String s = new String(char数组,起始下标,长度);
        String s = new String(byte数组);
        String s = new String(byte数组,起始下标,长度);
         */

        byte[] bytes = {97,98,99};
        String str2 = new String(bytes);
        System.out.println(str2);//abc,String类已经重写了toString方法,输出字符串对象,输出的是字符串本身

        String str3 = new String(bytes,1,2);//(字节数组,数组元素下标起始位置,长度)
        System.out.println(str3);//bc

        char[] chars = {'a','b','c','d','e'};
        //将char数组的一部分转换成字符串
        String str4 = new String(chars);
        System.out.println(str4);//abcde
        String str5 = new String(chars,2,3);
        System.out.println(str5);//cde
    }
}

3.3 String类的常用方法

 public class StringTest {
    public static void main(String[] args) {
        /*
        String类的常用方法
        char charAt(int index)
        int compareTo(String antherString)
        boolean contains(CharSequence s)
        boolean endsWith(String suffix)
        boolean equalsIgnoreCase(String anotherString)
        byte getBytes()
        int indexOf(String str)
        boolean isEmpty()
        int length()
        int lastIndexOf(String str)
        String replace(CharSequence target, CharSequence replacement)
        String[] split(String regex)
        boolean startsWith(String prefix)
        String substring(int beginIndex)
        char[] toCharArray()
        String toLowerCase()
        String trim()
        static String valueOf()
         */

        char c = "中国人".charAt(1);
        System.out.println(c);//国

        System.out.println("abc".compareTo("abc"));//返回0,按字典顺序比较两个字符串
        System.out.println("abcd".compareTo("abce"));//返回-1,前小后大负数
        System.out.println("abce".compareTo("abcd"));//返回1,前大后小正数

        System.out.println("HelloWorld.java".contains(".java"));//true,判断前面是否包含后面的字符串

        System.out.println("text.txt".endsWith(".java"));//false,判断当前字符串是否以某个字符串结尾

        System.out.println("Hello".equalsIgnoreCase("hello"));//true,判断两个字符串是否相等,忽略大小写

        byte[] bytes = "abcdef".getBytes();//将字符串对象转换成字节数组
        for (int i = 0;i < bytes.length;i++){
            System.out.println(bytes[i]);//97 98 99 100 101 102
        }

        System.out.println("oraclejavac++.netphppython".indexOf("c+"));//10 判断某个字符串在当前字符串中第一次出现处的索引

        System.out.println("".isEmpty());//true,判断字符串是否为空

        System.out.println("abc".length());//3,判断字符串长度,字符串长度的length()是方法,判断数组的length是属性

        System.out.println("oraclejavac++.netphppython".lastIndexOf("oracle"));//10 判断某个字符串在当前字符串中最后一次出现处的索引

        System.out.println("www.baidu.com".replace("baidu","jd"));//www.jd.com,将字符串中与文字目标匹配的每个子字符串替换为指定的文字

        System.out.println();//以”-“为分隔符拆分字符串
        String[] s = "1980-10-1".split("-");
        for (int i = 0;i < s.length;i++){
            System.out.println(s[i]);//1980 10 1
        }

        System.out.println("text.txt".endsWith("text"));//true,判断当前字符串是否以某个字符串开头

        System.out.println("www.baidu.com".substring(4));//baidu.com,截取一段字符串
        System.out.println("www.baidu.com".substring(4,9));//baidu.com,截取一段字符串,左闭右开

        char[] chars = "我是中国人".toCharArray();
        for (int i = 0;i < chars.length;i++){
            System.out.println(chars[i]);//我 是 中 国 人,将字符串转换成char数组
        }

        System.out.println("ABCDEFg".toLowerCase());//abcdefg,转换为小写
        System.out.println("abcdefg".toUpperCase());//ABCDEFG,转换为大写

        System.out.println("  Hello   World  ".trim());//去除字符串前后空白,不会去掉中间空白

        String s1 = String.valueOf(true);//将非字符串转换为字符串
        System.out.println(s1);//true,这是个字符串
        String s2 = String.valueOf(new Customer());
        System.out.println(s2);//内存地址,自动调用了toString()方法
    }
}
class Customer{

}

3.4 StringBuffer字符串拼接

import java.lang.StringBuffer;
public class StringBufferTest {
    public static void main(String[] args) {
        //在实际开发中,需要进行字符串的频繁拼接,这样会占用大量方法区内存,造成内存空间的浪费
        //如果进行大量字符串拼接使用JDK中的
        // java.lang.StringBuffer
        // java.lang.StringBuilder

        /*
        优化StringBuffer的性能:在创建StringBuffer时,尽可能的给定一个初始化容量,减少底层数组的扩容次数
         */
        
        //创建一个初始化容量为16的byte[]数组(字符串缓冲区对象)
        StringBuffer stringBuffer = new StringBuffer();
        //拼接字符串统一用append()方法,追加的意思,如果byte数组满了,会自动扩容
        stringBuffer.append("a");
        stringBuffer.append("b");
        System.out.println(stringBuffer);
    }
}
//StringBuffer和StringBuilder的区别
//StringBuffer是线程安全的,有synchrinized修饰,在多线程环境下运行是安全的。StringBuilder是非线程安全的

3.5 基本数据类型对应的8个包装类

为什么要使用包装类型?

public class IntegerTest00 {
    public static void main(String[] args) {
        //调用doSome()方法时需要传一个数字进去,但是数字属于基本类型,Object属于引用数据类型
        //此时可以对int进行一个包装将数字包装为对象
        MyInt myInt = new MyInt(100);
        doSome(myInt);
    }
    public static void doSome(Object obj){
        System.out.println(obj.toString());
    }
}

MyInt手写包装类

public class MyInt {
    int value;

    public MyInt() {
    }

    public MyInt(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return String.valueOf(value);
    }
}

八个包装类

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

        /*
        8种基本数据类型对应的包装类型名
        byte    java.lang.Byte(父类Number)
        short   java.lang.Short(父类Number)
        int     java.lang.Integer(父类Number)
        long    java.lang.Long(父类Number)
        float   java.lang.Float(父类Number)
        double  java.lang.Double(父类Number)
        boolean java.lang.Boolean(父类Object)
        char    java.lang.Character(父类Object)
        重点学习Integer,其他基本一致
        Number是一个抽象类,无法实例化对象
         */

        Integer i =new Integer(123);
        
        //基本数据类型转换为引用数据类型叫装箱
        float f = i.floatValue();
        System.out.println(f);//123.0

        //引用数据类型转换为基本数据类型叫拆箱
        int retValue = i.intValue();
        System.out.println(retValue);//123

    }
}

Integer的构造方法有:两个

integer(int i);

Integer(String s);

public class IntegerTest02 {
    public static void main(String[] args) {
        //将数字100转换为Integer包装类型
        Integer x = new Integer(100);
        System.out.println(x);
        //将String类型转换为Integer包装类型
        Integer y = new Integer("123");
        System.out.println(y);
        //将数字1.23转换为Double包装类型
        Double z = new Double(1.23);
        System.out.println(z);
        //将String类型转换为Double包装类型
        Double e = new Double("3.14");
        System.out.println(e);

        //通过常量值获取最大值和最小值
        System.out.println("int的最大值" + Integer.MAX_VALUE);
        System.out.println("int的最小值" + Integer.MIN_VALUE);
        System.out.println("byte的最大值" + Byte.MAX_VALUE);
        System.out.println("byte的最小值" + Byte.MIN_VALUE);
    }
}

自动装箱和自动拆箱

public class IntegerTest03 {
    public static void main(String[] args) {
        //在1.5之后,支持自动装箱拆箱了,有了自动拆箱后,Number中的方法就用不到了
        //自动装箱:将int类型转换为Integer
        Integer x = 100;
        //自动拆箱:将Integer类型转换为int
        int y = x;

        Integer z = 900;//z是个对象
        System.out.println(z + 1);//在这里z会自动拆箱变为基本数据类型

        Integer a = 1000;//Integer a = new Integer(1000);a是个引用,保存内存地址指向对象
        Integer b = 1000;//Integer b = new Integer(1000);b是个引用,保存内存地址指向对象
        System.out.println(a == b);//false,==比较多是内存地址,双等号不会自动拆箱

        Integer c = 127;
        Integer d = 127;
        System.out.println(c == d);//true
        /*
        Integer类加载的时候会初始化整数型常量池:256个对象
        Java为了提高效率,将[-128~127]之间的包对象提前创建好,放到一个方法区的整数型常量池中,其实池就是缓存cache
        ,只要是使用这个区间的数据就不需要再new了,直接从常量池中取出,所以c和d变量双等结果是true
         */

    }
}

Integer常用方法

public class IntegerTest04 {
    public static void main(String[] args) {
        /*
        空指针异常:NullPointerException
        类型转换异常:ClassCastException
        数组下标越界异常:ArrayIndexOutOfBoundsException
        数字格式化异常:NumberFormatException
         */


        //Integer常用方法

        //自动装箱
        Integer x = 1000;
        //手动拆箱
        int y = x.intValue();
        System.out.println(y);//1000

        Integer a = new Integer("中文");//不是一个数字可以包装成Integer对象吗?不能,会运行异常NumberFormatException
        int b = a.intValue();
        System.out.println(b);

        //static int   parseInt(String s)
        int retValue = Integer.parseInt("123");//String转换成int
        System.out.println(retValue + 100);//网页文本框中输入的其实是字符串,但是数据库要求保存数字,此时用parseInt()方法

        /*
        ---------------------------------------------------------------------------------
        以下作为了解
        static String toBinaryString(int i) 十进制转二进制
        static String toHexString(int i) 十进制转十六进制
        static String toOctalString(int i) 十进制转八进制
        static Integer valueOf(int i)
        static Integer valueOf(String s)
         */

    }
}

String int Integer三种类型互相转换

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

        //String转int
        int i1 = Integer.parseInt("100");//i1是数字100
        System.out.println(i1 + 1);//101

        //int转String

        String s2 = i1 +"";//"100"字符串
        System.out.println(s2 + 1);//"1001"
        //int转Integer
        Integer x = 1000;

        //Integer转int
        int y = x;


        //String转Integer
        Integer k = Integer.valueOf("123");
        //Integer转String
        String e = String.valueOf(k);
    }
}

3.6 Java语言中对日期的处理

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTest00 {
    public static void main(String[] args) throws Exception{
        /*
        简单总结System的方法
        System.out out是Ststem类的静态变量
        System.out.println() println()不是System类的,是PrintStream类的方法
        System.gc() 建议启动垃圾回收器
        System.currentTimeMillis() 获取自1970 1 1到系统当前时间的总毫秒数
        System.exit(0)  推出JVM
         */



        //获取系统当前时间
        Date nowTime = new Date();//精确到毫秒的系统当前时间
        System.out.println(nowTime);

        //SimpleDateFormat是java.text包下的,专门负责日期格式化
        /*
        yyyy 年
        MM 月
        dd 日
        HH 时
        mm 分
        ss 秒
        SSS 毫秒
        在日期格式中,除了以上符号不能修改外,其他符号随意组合
         */
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String nowTImestr = sdf.format(nowTime);
        System.out.println(nowTImestr);


        //有一个字符串类型的日期,怎么转换为Date类型
        String time = "2008-08-08 08:08:08 888";//格式不能随便写,必须前后对应
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date dateTime = sdf2.parse(time);
        System.out.println(dateTime);

        //获取自1970年1月1日 零时零分零秒到当前时间的总毫秒数
        long nowTimeMillis = System.currentTimeMillis();
        System.out.println(nowTimeMillis);

        //需求:统计一个方法执行所需耗时
        long nowTimeMillis0 = System.currentTimeMillis();
        print();
        long nowTimeMillis1 = System.currentTimeMillis();
        System.out.println(nowTimeMillis1 - nowTimeMillis0);

        //1970年1月1日 0时0分0秒 1毫秒
        Date time0 = new Date(1);//参数是一个毫秒

        //获取昨天此时的时间
        Date time1 = new Date(System.currentTimeMillis() - 1000*60*60*24);
    }

    public static void print(){
        for(int i = 0;i<100;i++){
            System.out.println("i= " + i);
        }

    }
}

3.7 数字格式化

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class Test1 {
    public static void main(String[] args) {
        //DecimalFormat专门负责格式化数字
        /*
        # 代表任意数字
        , 代表千分位
        . 代表小数点
        0 代表不够时补0DecimalFormat
         */
        DecimalFormat df = new DecimalFormat("###,##.##");
        String s = df.format(1234.56);
        System.out.println(s);//1,234.56

        //BigDecimal 属于大数据,精度极高,不属于基本数据类型,属于对象引用数据类型,专门用在财务软件上
        BigDecimal v1 = new BigDecimal(100);//精度极高的100
        BigDecimal v2 = new BigDecimal(200);//精度极高的200
        //相加不能用加号
        BigDecimal v3 = v1.add(v2);
        System.out.println(v3);
    }
}

3.8 随机数

import java.util.Random;

public class test1 {
    public static void main(String[] args) {
        Random random = new Random();
        int num = random.nextInt();//随机生成一个int类型取值范围的随机数
        System.out.println(num);

        random.nextInt(101);//产生[0-100]的随机数,不包括101

        //生成五个不重复的随机数放到数组中
         int[] arr = new int[5];
         for(int i = 0;i < arr.length ;i++){
             arr[i] = -1;
         }
         int index = 0;
         while(index < arr.length){
             int number = random.nextInt(6);
             if(!contains(arr,number)){
                arr[index++] = number;
                //index++;
             }
         }
        for(int i = 0;i < arr.length ;i++){
            System.out.println(arr[i]);
        }

    }
    public static boolean contains(int[] arr,int key) {//专门表示数组中是否包含某个元素
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == key) {
                return true;
            }
        }
        return false;
    }
}

3.9 枚举

public class test01 {
    public static void main(String[] args) {
        //结果超过两种情况并且是可以一个一个列出的,建议使用枚举
        Result r =divide(10,0);
        System.out.println(r == Result.SUCCESS ? "计算成功" : "计算失败");
    }
    public static Result divide(int a,int b){
        try {
            int c = a / b;
            return Result.SUCCESS;
        }catch (Exception e){
            return Result.FAIL;
        }
    }
}
//枚举是一种引用类型,每一个值都可以看做是常量
enum Result{
    //SUCCESS是枚举Result类型中的一个值
    //FAIL是枚举Result类型中的一个值
    //枚举中的值可以看作是"常量"
    SUCCESS,FAIL
}

四、异常(未完待续)

4.1 概述

当程序执行时出现问题,Java将异常信息打印到控制台,程序员看到异常后进行修改,使程序更健壮

Java中异常以类和对象的形式存在

4.2 Java异常处理机制

Object类下有Throwable可抛出的

Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)

Exception下有两个分支:Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须对异常进行提前处理,否则报错)和RuntimeException:运行时异常(在编写程序阶段程序员可以预先处理也可以不管)

编译时异常(别名:受检异常或受控异常)和运行时异常(别名:未受检异常或非受控异常)都是发生在运行阶段。编译阶段异常是不会发生的

所有异常都是在运行时发生的,因为只有程序运行阶段才可以new对象,异常的发生就是new异常对象

编译时异常和运行时异常的区别:

? 编译时异常发生概率高

? 对于一些发生概率较高的异常,需要在运行之前对其进行预处理(出门前带伞防止生病)

? 运行时异常发生概率低

? 没必要对出门对闪电击中防范,没必要提前预处理

假设没有划分编译时异常和运行时异常,所有异常都预处理:

? 首先这样写,程序会绝对安全,但是编写太累

? Java语言中对异常处理包括两种方式:

? 在方法声明的位置上使用throws关键字,抛给上一级,,Java中异常如果一直上抛,最终给main方法,main方法继续上抛给jvm终止java程序的运行

? 使用try catch语句进行异常的捕捉

只要异常没有捕获,采用上报的方式,此后代码不会执行。try语句块中某一行出现异常,该行后面的代码不会执行

public class test00 {
    public static void main(String[] args) {
        System.out.println(100/0);
        //此处发生了ArithmeticException异常,底层对象new了一个ArithmeticException对象,
        // 然后抛出了,由于是main方法调用了100/0,所以这个异常抛给了main方法,main没有处理,
        // 自动抛给jvm,jvm终止程序执行

        //ArithmeticException属于运行时异常,在编写阶段不需要对这种异常预先处理
        
        //未执行
        System.out.println("Hello World");
    }
}
public class Test01 {
    public static void main(String[] args) {
        //调用doSome时必须对这种异常进行预先处理,否则报错
        //编译时异常必须预先处理
        doSome();

    }
    public static void doSome() throws ClassNotFoundException{
        //表示doSome在执行过程中有可能会出现ClassNotFoundException异常,
        // 直接父类是Exception所以ClassNotFoundException属于编译时异常
        System.out.println("doSome");
    }
}

异常处理的原理

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

//        doSome();

        //第一种处理方式在方法声明的位置上继续使用上抛,抛给调用者
        //第二种方式:try catch进行捕捉,捕捉等于把异常拦截了,把异常真正解决了,调用者是不知道的
        try{
            doSome();
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
    public static void doSome() throws ClassNotFoundException{

        System.out.println("doSome");
    }
}
import java.io.FileInputStream;

public class Test02 {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }
    private  static void m1(){
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }
    private  static void m2(){
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }
    private  static void m3(){
        //调用SUN jdk中某个类的构造方法
        //创建一个输入流对象,该流指向一个文件

        //有报错,因为构造方法有一个编译时异常
        //FileNotFindException父类是IOException,IOException父类是Exception,由此可知FileNotFindException是编译时异常
        new FileInputStream("C:\\Users\\41882\\Desktop\\harmony_develop");
    }
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Test02 {
    public static void main(String[] args) {
        //一般不建议在main方法上使用throws,因为一定会抛给jvm,jvm只会终止。
        //异常处理机制是为了增加程序的健壮性,异常发生了也不会影响程序执行。
        //一般建议使用try catch 进行捕捉 main就不要向上抛了
        System.out.println("main begin");
        try {//try尝试
            m1();
            //m1里出异常,下面就不会执行了
        }catch (FileNotFoundException e){
            //这个分支中可以使用e引用,e引用保存的内存地址就是new出来的异常对象的内存地址
            //catch是捕捉异常后走的分支
            //m1里出异常,执行这里
            System.out.println("文件不存在!");
        }

        System.out.println("main over");
    }
    private  static void m1() throws FileNotFoundException {
        System.out.println("m1 begin");
        m2();
        //m2里出异常,下面就不会执行了
        System.out.println("m1 over");
    }
    private  static void m2() throws FileNotFoundException {
        System.out.println("m2 begin");
        m3();
        //m3里出异常,下面就不会执行了
        System.out.println("m2 over");
    }
    private  static void m3() throws FileNotFoundException {
        //调用SUN jdk中某个类的构造方法
        //创建一个输入流对象,该流指向一个文件

        //有报错,因为构造方法有一个编译时异常
        //FileNotFindException父类是IOException,IOException父类是Exception,由此可知FileNotFindException是编译时异常
        //throws 父类也行,因为父类包括子类,可以多个异常逗号隔开
        new FileInputStream("C:\\Users\\41882\\Desktop\\harmony_develop");
        //如果上面那行代码出异常,下面代码就不会执行了
    }
}

五、 集合(未完待续)

六、 IO流(未完待续)

七、 多线程(未完待续)

八、 反射机制

8.1 反射的概述

8.1.1 什么是反射

? Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制

8.1.2 反射机制有什么用?

通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件)
通过反射机制可以操作代码片段(class文件)

8.1.3 反射机制相关的类在哪?

反射机制相关的类都在 java.lang.reflect.*

8.1.4 反射机制相关的主要的类有哪些?

  • java.lang.Class:代表整个字节码,代表一个类型,代表整个类

  • java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态+实例)

  • java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法

  • java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法

//Class    
public class User{
    // Field
    int no;
    // Constructor
    public User(){}
    // Constructor
    public User(int no){
        this.no = no;
    }
    // Method
    public void setNo(int no){
        this.no = no;
    }
    // Method
    public int getNo(){
        return no;
    }
}

8.2 获取Class的三种方式

要操作一个类的字节码,需要首先获取到这个类的字节码

获取java.lang.Class实例有三种方式:

  1. Class c = Class.forName("完整类名带包名");

  2. Class c = 对象.getClass();

  3. Class c = 任何类型.class;

第一种方式:forName()方法

Class c = Class.forName("完整类名带包名");

java.lang.Class.forName(String className)方法

  • 静态方法,返回值为Class
  • 方法参数为String字符串类型
  • 字符串需要完整类名(必须带有包名,java.lang也不能省)
  • 此方法会导致类加载

第二种方式:getClass()方法

Class c = 对象.getClass();

java.lang.Object.getClass()方法

  • Object类有getClass()方法,因此其他任何一个对象都存在此方法

第三种方式:类型.class

Class c = 任何类型.class;
  • java语言中任何一种类型,包括基本数据类型,它都有.class属性

三种获取类的代码示例:

package com.zzl.reflect;

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {
        /*
        第一种方法
        CLass.forName()
         */
        
        Class c1 = null;
        Class c2 = null;

        try {
            c1 = Class.forName("java.lang.String");//c1代表String.class文件,或者说代表String类型
            c2 = Class.forName("java.util.Date");//c2代表Date类型
            
            Class c3 = Class.forName("java.lang.Integer");//c3代表Integer类型
            Class c4 = Class.forName("java.lang.System");//c4代表System类型
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        /*
        第二种方法
        Java中任何一个对象都有一个方法:getClass()
        Object类有getClass()方法,所以其他对象都存在此方法
        */
        String s = "abc";
        Class x = s.getClass();//x代表的是String.class字节码文件,x代表String类型
        System.out.println(c1 == x);//true(==判断的是对象的内存地址)

        Date time = new Date();
        Class y = time.getClass();
        System.out.println(c2 == y);//true c2和y变量保存的内存地址都一样,都指向方法区中的字节码文件

        /*
        第三种方法
        Java中任何一种类型,包括基本数据类型,都有.class属性
        */
        Class z = String.class;
        Class k = Date.class;
        Class f = int.class;
        Class e = double.class;
        System.out.println(x == z);//true

    }
}

字节码内存图:

https://www.bilibili.com/video/BV1Rx411876f?p=812&t=488.2

image-20220210101756328

8.3 获取Java类能干什么?

通过反射实例化对象

java.lang.Class.newInstance()方法

  • 该方法会使用对应类的无参构造方法来创建该类的实例
  • 该方法要求该 Class 对应类有无参构造方法

通过Class的newInstance()方法来实例化对象

User类

package com.zzl.bean;

public class User {
    public User() {
        System.out.println("无参构造方法执行");
    }
    public User(String name) {
        System.out.println("有参构造方法执行");
    }
}
package com.zzl.reflect;
/*
*使用反射机制创建对象
* */
public class ReflectTest02 {
    public static void main(String[] args) {
        
        try {
            //通过反射机制,获取Class,通过Class来实例化对象
            Class c = Class.forName("com.zzl.bean.User");//c代表User类型
            
            //newInstance()方法会调用User这个类的无参构造方法,完成对象的创建
            //必须保证无参构造存在,否则会抛出实例化异常InstantiationException
            Object obj = c.newInstance();
            System.out.println(obj);
            /*
            无参构造方法执行
            com.zzl.bean.User@1b6d3586
            */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

8.4 为什么要用反射机制创建对象

不使用反射机制同样可以创建对象,但是为什么还要用反射机制创建呢?

因为反射实例化对象更灵活

8.4.1 验证反射机制的灵活性

reflect\classinfo.properties配置文件

className=com.zzl.bean.User
package com.zzl.reflect;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Properties;

/*
验证反射机制的灵活性
Java代码写一遍,在不改变源代码的基础上,可以做到不同对象的实例化
非常灵活,符合OCP原则:对扩展开放,对修改关闭

后期要学习高级框架ssh、ssm:
高级框架底层实现原理都采用了反射机制,学习反射有利于理解剖析框架底层源代码

 */
public class ReflectTest03 {
    public static void main(String[] args) throws Exception {

        //User user = new User();这种方式代码就写死了,只能创建一个User类

        
        //以下代码是灵活的,代码无需改动,可以只修改配置文件,创建出不同的实例化对象
        //通过IO流读取classinfo.properties文件
        FileReader reader = new FileReader("reflect/classinfo.properties");
        //创建属性类对象Map
        Properties pro = new Properties();//key value 都是String类型
        //加载
        pro.load(reader);
        //关闭流
        reader.close();

        //通过key获取value
        String className = pro.getProperty("className");

        //通过反射实例化对象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
        /*
        * 无参构造方法执行
        * com.zzl.bean.User@1b6d3586
        * */
    }
}

8.4.2 研究Class.forName()方法发生了什么?

package com.zzl.reflect;
/*
研究下 Class.forName()发生了什么?

如果仅希望一个类的静态代码块执行,其他代码一律不执行,
可以使用 Class.forName("完整类名")
此方法执行会导致类加载,类加载时,静态代码块会执行
在这里对该方法的返回值不感兴趣,只是为了使用类加载动作
 */
public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            //Class.forName()方法执行会导致:类加载
            Class.forName("com.zzl.reflect.MyClass");
            /*
            MyClass类的静态代码块执行了!
             */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
class MyClass{
    //静态代码块在类加载时执行,并且只执行一次
    static {
        System.out.println("MyClass类的静态代码块执行了!");
    }
    public MyClass() {//不会执行
        System.out.println("MyClass类无参构造执行了!");
    }
}

8.4.3 研究文件路径问题

image-20220210122359115

package com.zzl.reflect;

import java.io.FileNotFoundException;
import java.io.FileReader;

/*
研究一下文件路径的问题
怎么获取一个文件的绝对路径,下面的方式是通用的。但是有个前提:文件必须在类路径下。
 */
public class AboutPath {
    public static void main(String[] args) throws Exception {
        //以下方式的路径缺点是移植性差,在IDEA中默认的当前路径是project的根
        //代码离开IDEA换到其他位置就会出问题
        //FileReader reader = new FileReader("reflect/classinfo.properties");

        
        //以下方式比较通用,即使代码换位置
        //前提是这个文件必须在类路径下:即在src下
        //src是类的根路径

        /*
        解释
        Thread.currentThread()当前线程对象
        getContextClassLoader()是线程对象的方法,可以获取到当前线程的类加载器对象
        getResource() 获取资源,这是类加载器的方法,当前线程的类加载器默认从类的根路径下加载资源
         */

        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo2.properties").getPath();
        //采用以上代码可以拿到一个文件的绝对路径
        System.out.println(path);
        /*
        * /C:/Users/Spring/IdeaProjects/JaveSE/out/production/reflect/classinfo2.properties
        * */

        //获取db.properties文件的绝对路径(从类的根路径作为起点开始)
        String path2 = Thread.currentThread().getContextClassLoader()
                .getResource("com/zzl/bean/db.properties").getPath();
        System.out.println(path2);
        /*
        * /C:/Users/Spring/IdeaProjects/JaveSE/out/production/reflect/com/zzl/bean/db.properties
        * */
    }
}

直接以流的方式返回文件的绝对路径

package com.zzl.reflect;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InputStream;
import java.util.Properties;

public class IoPropertiesTest {
    public static void main(String[] args) throws Exception {

        //获取一个文件的绝对路径
//        String path = Thread.currentThread().getContextClassLoader()
//                .getResource("classinfo2.properties").getPath();
//        FileReader reader = new FileReader(path);

        //直接以流的方式返回文件绝对路径
        InputStream reader = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("classinfo2.properties");

        //创建属性类对象Map
        Properties pro = new Properties();//key value 都是String类型
        //加载
        pro.load(reader);
        //关闭流
        reader.close();

        //通过key获取value
        String className = pro.getProperty("className");
        System.out.println(className);//com.zzl.bean.User

    }
}

8.5 资源绑定器

java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容

  • 只能绑定xxx.properties文件
  • 这个文件必须在类路径下,以src为起点
  • 不能写上配置文件扩展名

db.properties配置文件

className=java.lang.String
package com.zzl.reflect;

import java.util.ResourceBundle;

/*
java.util包下提供了一个资源绑定器,便于获取属性配置文件的内容
使用以下方式时,属性配置文件xxx.properties必须放到类路径下,都是以src为起点
 */
public class ResourceBundleTest {
    public static void main(String[] args) {

        //资源绑定器,只能绑定xxx.properties文件。并且这个文件必须在类路径下
        //文件拓展名也必须为properties,在写路径时不能写上扩展名
        //ResourceBundle bundle = ResourceBundle.getBundle("classinfo2");
        
        //获取db.properties配置文件内的className的值
        ResourceBundle bundle = ResourceBundle.getBundle("com/zzl/bean/db");
        String className = bundle.getString("className");
        System.out.println(className);//java.lang.String
    }
}

8.6 类加载器概述(了解)

假设有这样一段代码:

String s = "abc";

? 代码在开始执行之前,会将所需要类全部加载到JVM当中

? 通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?

  1. 首先通过“启动类加载器”加载

    启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar

    rt.jar中都是JDK最核心的类库。

  2. 如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载

    扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar

  3. 如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载

    应用类加载器专门加载:classpath中的类

双亲委派机制

java中为了保证类加载的安全,使用了双亲委派机制

优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。

双亲委派机制:如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

8.7 获取Field(了解)

Student类

package com.zzl.bean;

//反射属性Field

public class Student {
    //Field翻译为字段,其实就是属性/成员
    //四个Field,分别采用不同的访问控制权限修饰符
    
    public int no;//Field对象
    private String name;//Field对象
    protected int age;//Field对象
    boolean sex;//Field对象
    private static final double MATH_PI = 3.1415926;
}
package com.zzl.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/*
反射Student类中的所有Field
 */
public class ReflectTest05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取整个类
        Class studentClass= Class.forName("com.zzl.bean.Student");
        System.out.println("完整类名" + studentClass.getName());//完整类名com.zzl.bean.Student
        System.out.println("简单类名" + studentClass.getSimpleName());//简单类名Student

        //获取类中所有public修饰的Field
        Field[] fields = studentClass.getFields();
        System.out.println(fields.length);//1
        Field f = fields[0];
        String fieldName = f.getName();
        System.out.println(fieldName);

        System.out.println("----------------分割线-------------------------");
        
        //获取所有的Field
        Field[] fs = studentClass.getDeclaredFields();//Class.getDeclaredFields()返回字段对象的数组
        System.out.println(fs.length + "个");//5个
        System.out.println("--------");
        for (Field field:fs){
            //获取属性名字
            System.out.println("属性名为" + field.getName());
            //获取属性类型
            Class fieldType = field.getType();//Field.getType()返回类对象,该对象标识此字段对象表示的字段的声明类型
            String fName = fieldType.getName();//name为String类型,想返回简单名字可以用getSimpleName()方法
            System.out.println("类型为" + fName);
            //获取属性的修饰符列表
            int i = field.getModifiers();//因为修饰符列表可能不仅一个,所以有s.返回值为int
            //Field.getModifiers()以整数形式返回此字段对象表示的字段的Java语言修饰符
            System.out.println("修饰符代号为"+i);//返回的是修饰符的代号
            
            String modifierString = Modifier.toString(i);
            //java.lang.reflect.Modifier.toString(int mod)返回描述指定修饰符中的访问修饰符标志的字符串
            
            System.out.println("修饰符列表为" + modifierString);
            System.out.println("--------");
     		
        }
    }
}
/* 完整结果:

完整类名com.zzl.bean.Student
简单类名Student
1
no
----------------分割线-------------------------
5个
--------
属性名为no
类型为int
修饰符代号为1
修饰符列表为public
--------
属性名为name
类型为java.lang.String
修饰符代号为2
修饰符列表为private
--------
属性名为age
类型为int
修饰符代号为4
修饰符列表为protected
--------
属性名为sex
类型为boolean
修饰符代号为0
修饰符列表为
--------
属性名为MATH_PI
类型为double
修饰符代号为26
修饰符列表为private static final
--------
/*

8.8 反编译Field(了解)

package com.zzl.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

//通过反射机制,反编译一个类的属性Field
public class ReflectTest06 {
    public static void main(String[] args) throws Exception {
        //创建这个字符串只是为了拼接字符串
        StringBuilder s = new StringBuilder();
        
        //获取类
        Class studentClass = Class.forName("com.zzl.bean.Student");
        //Class studentClass = Class.forName("java.lang.String");

        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + "{\n");

        Field[] fields = studentClass.getDeclaredFields();
        for(Field field:fields){
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            s.append(field.getType().getSimpleName());
            s.append(" ");
            s.append(field.getName());
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
        /*
        public class Student{
            public int no;
            private String name;
            protected int age;
             boolean sex;
            private static final double MATH_PI;
}
         */

    }
}

8.9 通过反射机制访问对象的某个属性(重点掌握)

package com.zzl.reflect;

import com.zzl.bean.Student;

import java.lang.reflect.Field;

/*
必须掌握:
怎么通过反射机制访问一个Java对象的属性?
    给属性赋值set
    获取属性的值get
 */
public class ReflectTest07 {
    public static void main(String[] args) throws Exception {
        //不使用反射如何访问属性的值
        /*Student s = new Student();
        s.no = 10;
        System.out.println(s.no);*/

        //使用反射机制,怎么去访问一个对象的属性
        Class studentClass = Class.forName("com.zzl.bean.Student");
        Object obj = studentClass.newInstance();//此时obj就是Student对象,底层调用了无参构造方法

        //获取属性靠名字区分
        //获取no属性
        Field noField = studentClass.getDeclaredField("no");
        //Class.getDeclaredField(String name)方法可以获取指定属性
        
        //给obj对象的no属性赋值
        /*
        虽然使用了反射机制,但是三要素缺一不可
            1.obj对象
            2.no属性
            3.值
        注意:反射机制让代码复杂了,但是为了"灵活"值得!
        实际开发中写反射机会很少
        */
        noField.set(obj,11);//Field.set(Object obj, Object value)将obj类的noField属性赋值为value
        //读obj的值
        //两个要素:获取obj对象的no属性值
        System.out.println(noField.get(obj));//noField.get(obj)得到obj类的noField属性的值,返回值为Object类型,直接输出11

        
        //可以访问私有属性吗?
        //获取name属性
        Field nameField = studentClass.getDeclaredField("name");
        
        //打破封装:不打破封装访问不了,抛出IllegalAccessException
        //打破封装会给不法分子留下机会
        //打破封装后,在外部也可以访问private
        nameField.setAccessible(true);
        //Field.setAccessible(boolean flag) 将此反射对象的 accessible标志设置为指示的布尔值
        
        //给name属性赋值
        nameField.set(obj,"John");
        //读取输出name值
        System.out.println(nameField.get(obj));
    }

}

8.10 可变长度参数

  1. 可变长度参数要求参数个数为0到n
  2. 可变长度参数在参数列表中必须在最后,且只能有一个
  3. 可变长度参数可以当数组来看待
package com.zzl.reflect;

/*
可变长度参数
    int ... args 这就是可变长度参数
    语法是... (一定是三个点!)
* */
public class ArgsTest {
    public static void main(String[] args) {
        m();
        m(10);
        m(10,20);
        //m("abc");必须为int类型

        m1(10,"abc","def");
        m2("aa","bb","cc");
        String[] strs = {"a","b","c"};
        //也可以传一个数组
        m2(strs);
        m2(new String[]{"ni","hao"});//没必要,直接以下就行
        m2("ni","hao");
        /*
        1.可变长度参数要求参数个数为0到n
        2.可变长度参数在参数列表中必须在最后,且只能有一个
        3. 可变长度参数可以当数组来看待
        * */
    }
    public static void m(int... args){//可0可n
        System.out.println("m方法执行了");
    }
    public static void m1(int a,String... args1){//可1可n
        System.out.println("m1方法执行了");
    }
    public static void m2(String... args){
        //args竟然有length属性,说明args是一个数组

        for(int i = 0;i < args.length;i++){
            System.out.println(args[i]);
        }
    }
}

8.11 反射Method

UserService类

package com.zzl.service;
/*
    用户业务类
* */
public class UserService {
    int no;
    int age;

    public boolean login(String name,String password){
        if("admin".equals(name)&&"123".equals(password)){
            System.out.println("登录成功");
            return true;
        }
        return false;
    }

    public void login(int i){
    }

    public void logout(){
        System.out.println("系统已经安全退出!");
    }
}

package com.zzl.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/*
作为了解内容:反射Method
* */
public class ReflectTest08 {
    public static void main(String[] args) throws Exception {
        //获取类
        Class userServiceClass = Class.forName("com.zzl.service.UserService");

        //获取所有的Method(包括私有的)
        Method[] methods = userServiceClass.getDeclaredMethods();
        System.out.println("方法个数有" + methods.length);//2
        //遍历Method
        System.out.println("--------");
        for(Method method:methods){
            //获取修饰符列表
            System.out.println("修饰符列表" + Modifier.toString(method.getModifiers()));
            //获取方法的返回值类型
            System.out.println("返回值类型为" + method.getReturnType());
            //获取方法名
            System.out.println("方法名为" + method.getName());
            //获取方法中的参数
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType:parameterTypes){
                //获取参数类型
                System.out.println("参数类型为" + parameterType.getSimpleName());
            }
            System.out.println("--------");

        }
    }
}

8.12 反编译Method

package com.zzl.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/*
了解一下,不需要掌握(反编译一个类的方法。)
 */
public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class userServiceClass = Class.forName("com.zzl.service.UserService");
//        Class userServiceClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");

        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            //public boolean login(String name,String password){}
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            // 参数列表
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            if(parameterTypes.length > 0){
                // 删除指定下标位置上的字符
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}

8.13 反射机制调用方法(重点掌握)

package com.zzl.reflect;

import com.zzl.service.UserService;

import java.lang.reflect.Method;

/*
通过反射机制怎么调用一个对象的方法

反射机制让代码具有通用性,可变化的内容都放到配置文件中,将来仅需要修改配置文件,而Java代码保持不变
* */
public class ReflectTest10 {
    public static void main(String[] args) throws Exception {

        //不使用反射调用方法
        /*
        UserService userService = new UserService();
        boolean loginSuccess = userService.login("admin","123");
        System.out.println(loginSuccess?"登录成功":"登录失败");

        调用方法要素分析:
        1.对象
        2.login方法
        3.实参列表
        4.返回值
        */

        //使用反射机制怎么调用方法
        //先获取一个类
        Class userServiceClass = Class.forName("com.zzl.service.UserService");
        //创建对象
        Object obj = userServiceClass.newInstance();

        //获取Method
        //Java中区分一个方法是通过方法名和参数列表
        Method loginMethod = userServiceClass.getDeclaredMethod("login",String.class,String.class);
        //Method loginMethod = userServiceClass.getDeclaredMethod("login",int.class);
        //userServiceClass.getDeclaredMethod("login",String.class,String.class);中后面的参数类型是Class类型

        Object retValue = loginMethod.invoke(obj,"admin","123");//invoke()是调用函数,反射机制中最重要的方法
        System.out.println(retValue);//true


    }
}

8.14 反射Constructor

Vip类

package com.zzl.bean;

/*
反编译一个类的Constructor构造方法
* */
public class Vip {
    int no;
    String name;
    String birth;
    boolean sex;

    public Vip() {
    }

    public Vip(int no) {
        this.no = no;
    }

    public Vip(int no, String name) {
        this.no = no;
        this.name = name;
    }

    public Vip(int no, String name, String birth) {
        this.no = no;
        this.name = name;
        this.birth = birth;
    }

    public Vip(int no, String name, String birth, boolean sex) {
        this.no = no;
        this.name = name;
        this.birth = birth;
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Vip{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", birth='" + birth + '\'' +
                ", sex=" + sex +
                '}';
    }
}

package com.zzl.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ReflectTest11 {
    public static void main(String[] args) throws Exception {

        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("com.zzl.bean.Vip");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");

        // 拼接构造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            // 拼接参数
            Class[] parameterTypes = constructor.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除最后下标位置上的字符
            if(parameterTypes.length > 0){
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }

        s.append("}");
        System.out.println(s);
    }
}

8.15 反射机制调用构造方法

package com.zzl.reflect;

import com.zzl.bean.Vip;

import java.lang.reflect.Constructor;

/*
比上一个例子(ReflectTest11)重要一些!!!

通过反射机制调用构造方法实例化java对象。(这个不是重点)
 */
public class ReflectTest12 {
    public static void main(String[] args) throws Exception {
        // 不使用反射机制怎么创建对象
//        Vip v1 = new Vip();
//        Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);

        // 使用反射机制怎么创建对象呢?
        Class c = Class.forName("com.zzl.bean.Vip");
        // 调用无参数构造方法
        Object obj = c.newInstance();
        System.out.println(obj);

        // 调用有参数的构造方法怎么办?
        // 第一步:先获取到这个有参数的构造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
        // 第二步:调用构造方法new对象
        Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
        System.out.println(newObj);

        // 获取无参数构造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();
        System.out.println(newObj2);

    }
}

8.16 获取父类和父接口

package com.zzl.reflect;

/*
重点:给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
 */
public class ReflectTest13 {
    public static void main(String[] args) throws Exception {
        // String举例
        Class stringClass = Class.forName("java.lang.String");

        // 获取String的父类
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName());

        // 获取String类实现的所有接口(一个类可以实现多个接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for(Class in : interfaces){
            System.out.println(in.getName());
        }
    }
}

九、 注解

注解(Annotation)又叫注释类型

注解是一种引用数据类型。编译后也是生成xxx.class文件

如何定义注解?语法格式

[修饰符列表] @interface 注解类型名{
    
}

注解怎么使用?用在什么地方?

  1. 注解使用时的语法格式是

    @注解类型名
    
  2. 注解还可以出现在类上、属性上、方法上、变量上、接口上等等,注解还可以出现在注解类型上

package com.zzl.annotation;

/*
*自定义注解
*/
public @interface MyAnnotation {

}
package com.zzl.annotation;
/*
* 注解修饰注解
* */
@MyAnnotation
public @interface OtherAnnotation {
}

package com.zzl.annotation;
/*
* 默认情况下注解可以出现在任意位置
* */

@MyAnnotation
public class AnnotationTest01 {

    @MyAnnotation
    private int no;

    @MyAnnotation
    public AnnotationTest01(){}

    @MyAnnotation
    public static void m1(){
        @MyAnnotation
        int i = 100;
    }

    @MyAnnotation
    public void m2(@MyAnnotation String name){}
}

@MyAnnotation
interface MyInterface{}

@MyAnnotation
enum Season{
    SPRING,SUMMER,AUTUMN,WINTER
}

JDK内置了哪些注解?

java.lang包下的注释类型:

  • 掌握:
    • Deprecated用@Deprecated注释的程序元素
    • 不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择
  • 掌握:
    • Override表示一个方法声明打算重写超类中的另一个方法声明
  • 不用掌握
    • SupperssWarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告

Override注解

package com.zzl.annotation;
/*
* 关于JDK lang包下的Override注解
*源代码为:
* public @interface Override {}
*
* 标识性注解,给编译器作参考的。
* 编译器看到方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法
* 如果没有重写会报错
*
*
*
*
* @Override这个注解只能注解方法
* @Override这个注解是给编译器参考的,和运行阶段没有关系
* 凡是Java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写方法,编译器就会报错
*
* */
//@Override不放在类上
public class AnnotationTest02 {

    @Override
    public String toString() {
        return "toString";
    }
}

/*
* @Override注解被以下两个注解所注解:
* @Target(ElementType.METHOD)
* @Retention(RetentionPolicy.SOURCE)
* */

元注解

什么是元注解?

用来标注“注解”的“注解”,称为元注解

常用的元注解有哪些?

Target

Retention

关于Target注解:

这是一个元注解,用来标注“注解类型”的“注解”

这个Target注解用来标注“被标注的注解可以出现在哪些位置上”

@Target(ElementType.METHOD)//这个"被标注的注解"只能出现在方法上
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})//该注解可以出现在:构造方法上、字段上、局部变量上、方法上....

关于Retention注解:

这是一个元注解,用来标注“注解类型”的“注解”

这个Retention注解用来标注“被标注的注解”最终保存到哪里

@Retention(RetentionPolicy.SOURCE)//表示该注解只被保留在Java源文件中
@Retention(RetentionPolicy.CLASS)//表示该注解被保留在class文件中
@Retention(RetentionPolicy.RUNTIME)//表示该注解被保留在class文件中,并且可以被反射机制所读取

@Deprecated注解

package com.zzl.annotation;
/*
* @Deprecated
* 表示标注的元素已经过时
* 这个注解主要是向其他程序员传达一个信息,告知已过时,有更好的解决方案
* 编译器可以捕捉到过时的东西并且Warning,@Deprecated说明不仅保存在Java源文件中
* */

public class AnnotationTest03 {
    public static void main(String[] args) {
        AnnotationTest03 at = new AnnotationTest03();
        at.doSome();
    }
    @Deprecated//表示这个方法已过时
    public void doSome(){
        System.out.println("doSome!");
    }
    @Deprecated//表示这个方法已过时
    public static void doOther(){
        System.out.println("doOther!");
    }
}
class T {
    public static void main(String[] args) {
        AnnotationTest03 at = new AnnotationTest03();
        at.doSome();//doSOme()已过时
        AnnotationTest03.doOther();//doOther()已过时
    }
}

注解中定义属性

package com.zzl.annotation2;

public @interface MyAnnotation {

    //我们通常在注解中可以定义属性
    //以下是MyAnnotation的name属性
    //看着像一个方法,实际上称为属性name
    String name();

    String color();
    int age() default 25;//属性指定值后,使用注解时就可以不写此属性了
}
package com.zzl.annotation2;

public class MyAnnotationTest {

    //报错原因,如果一个注解中有属性,那么必须给属性赋值
    //除非该属性使用default指定了默认值

    //@MyAnnotation(属性名=属性值)
    @MyAnnotation(name = "zhangsan",color = "red")
    @Deprecated()
    public void doSome(){


    }
}

属性名为value时可以省略属性名

package com.zzl.annotation3;

public @interface MyAnnotation {
    //指定一个value属性
    String value();

//    String name();
}
package com.zzl.annotation3;

public class MyAnnotationTest {

    //如果一个属性名为value且仅有value一个属性时(包括value[]),在使用时该属性名可以省略,仅写属性值
    //如果有value和其他属性,那么就都必须写好属性名
    @MyAnnotation("haha")
    public void doSOme(){

    }
}

注解中属性可以是哪一种类型?

package com.zzl.annotation4;

public @interface MyAnnotation {

    /*
    * 注解中属性可以是哪一种类型?
    *  byte short int long float double boolean char String Class 枚举类
    *  以及以上每一种的数组形式
    * */
    int value1();
    String value2();
    int[] value3();
    String[] value4();
    Class parameterType();
    Class[] parameterTypes();
}

当属性是一个数组时

package com.zzl.annotation4;

public @interface OtherAnnotation {
    int age();
    String[] email();
    Season[] seasonArray();
}

package com.zzl.annotation4;

public enum Season {
    SPRING,SUMMER,AUTUMN,WINTER
}
package com.zzl.annotation4;

public class OtherAnnotationTest {


    @OtherAnnotation(age = 25, email = {"zhangsan@qq.com","lisi@sohu.com"},seasonArray = {Season.SPRING,Season.SUMMER})
    public void doSome(){

    }

    //当数组中只有一个元素,大括号可以省略
    @OtherAnnotation(age = 25, email = "zhangsan@qq.com",seasonArray = Season.SPRING)
    public void doOther(){

    }
}

源代码分析

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})

//@Target中有且仅有一个属性为value数组,且数组元素类型为枚举类型
//value数组也可以省略属性名使用,此处未省略

public @interface Deprecated {
}

源代码分析

@Documented
@Retention(RetentionPolicy.RUNTIME)

//@Retention中有且仅有一个属性为value,且属性值为枚举类型


@Target(ElementType.ANNOTATION_TYPE)

//@Target中有且仅有一个属性为value数组,且数组元素类型为枚举类型


public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

反射注解

package com.zzl.annotation5;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//只允许该注解标注类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
//希望该注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {

    String value() default "北京市";
}

package com.zzl.annotation5;

@MyAnnotation
public class MyAnnotationTest {

    //@MyAnnotation
    int i;

    //@MyAnnotation不可以出现在构造方法上
    public MyAnnotationTest() {
    }

    @MyAnnotation
    public void doSome(){
        //@MyAnnotation
        int i;
    }
}

package com.zzl.annotation5;


//获取类上的注解
public class ReflectAnnotationTest {
    public static void main(String[] args) throws Exception {
        //获取类
        Class c = Class.forName("com.zzl.annotation5.MyAnnotationTest");
        //判断类上是否有@MyAnnotation
        //System.out.println(c.isAnnotationPresent(MyAnnotation.class));//true


        //如果注解仅保存在Java源文件中,反射机制抓取不到,if()条件不成立
        if(c.isAnnotationPresent(MyAnnotation.class)){//如果有注解就获取到
            //获取该注解对象
            MyAnnotation myAnnotation = (MyAnnotation) c.getAnnotation(MyAnnotation.class);
            System.out.println("类上面的注解对象" + myAnnotation);
            //获取注解对象的属性怎么办?和调接口没区别
            String value = myAnnotation.value();//北京市
            System.out.println(value);

        }

        //判断String类上是否有@MyAnnotation
        Class stringClass = Class.forName("java.lang.String");
        //判断类上是否有@MyAnnotation
        System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class));//false
    }

}

通过反射机制获取注解对象属性的值

package com.zzl.annotation6;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String username();
    String password();
}
package com.zzl.annotation6;


/*
* 通过反射机制获取注解对象属性的值
* */

import java.lang.reflect.Method;

public class MyAnnotationTest {
    @MyAnnotation(username = "admin",password = "123")
    public void doSome(){

    }

    public static void main(String[] args) throws Exception {
        //获取MyAnnotationTest的doSome()方法上的注解信息
        Class c = Class.forName("com.zzl.annotation6.MyAnnotationTest");
        //获取doSome()方法
        Method doSomeMethod = c.getDeclaredMethod("doSome");
        //判断该方法上是否存在这个注解
        if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)){
            //拿到这个注解
            MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
            System.out.println(myAnnotation.username());
            System.out.println(myAnnotation.password());

        }

    }
}

注解在实际开发中有什么用?

需求:

? 假设有这样一个注解,叫做:@id

? 这个注解只能出现在类上面,当这个类有这个注解时,要求这个类必须有一个int类型的id属性。如果没有这个属性就报异常。如果有这个属性就正常进行

package com.zzl.annotation7;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//表示这个注解只能出现在类上面
@Target(ElementType.TYPE)
//该注解可以被反射机制读取到
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
    /*
    * 这个注解只能出现在类上面,当这个类有这个注解时,要求这个类必须有一个int类型的id属性。如果没有这个属性就报异常
    * */


}

package com.zzl.annotation7;


@Id
public class User {
    int id;
    String name;
    String password;

}

package com.zzl.annotation7;

import java.lang.reflect.Field;

public class Test {

    public static void main(String[] args) throws Exception {
        //获取类
        Class userClass = Class.forName("com.zzl.annotation7.User");

        boolean isOk = false;//给一个默认的标记
        //判断类上是否有Id注解
        if(userClass.isAnnotationPresent(Id.class)){
            //获取类的属性
            Field[] fields = userClass.getDeclaredFields();
            for(Field field:fields){
                if("id".equals(field.getName())&&"int".equals(field.getType().getSimpleName())){
                    isOk = true;//表示合法
                    break;
                }
            }

            //判断是否合法
            if(!isOk){
                throw new HasNotIdPropertyException("被@Id注解标注的中中必须有一个it类型的属性");
            }
        }


    }



}

相关