Java基础课程-数组-反射机制

1.上章回顾与预习检查

1.1 上章回顾

  • 接口与抽象类的区别?
  • 什么是内部类?
  • 谈谈你对回调函数的理解?

1.2 预习检查

  • 什么是数组?
  • 数组中是length属性还是length()方法?
  • 数组的下标从几开始?
  • 什么是反射?

2. 本章任务

  • 解剖数组;
  • 数组排序;
  • 学生成绩等级划分;
  • 杨辉三角;
  • 解剖类;

3. 本章内容

  • 数组
  • 反射机制

1.1 数组

1.1.1 数组概述

所谓数组,就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。数组是在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来的一种形式。这些按序排列的同类数据元素的集合称为数组。

归纳如下:

  • 数组是多个相同类型数据的数据结构,实现对这些数据的统一管理
  • 数组属引用类型,数组型数据是对象(object),数组中的每个元素相当于该对象的成员变量
  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型

1.1.2 一维数组声明

type var[] 或 type[] var;
代码示例1:

    int a[];
    int[] a1;
    double  b[];
    Mydate []c;

注意:
Java语言中声明数组时不能指定其长度(数组中元素的个数).
代码示例2:

    int a[5];    //非法

1.1.3 一维数组的创建

声明了一个一维数组,仅仅是给出了数组名称和元素的数据类型,要想真正使用数组还必须为它内存空间,即创建数组。在为数组分配内存空间时,必须指明数组的长度。创建数组要用到运算符new.

代码示例3:

arrayName = new type[size]; 如:
page = new int[3];

创建一维数组内存模式如下图:
这里写图片描述

1.1.4 数组元素的引用

定义并用运算符new为之分配空间后后,才可以引用数组中的每个元素;
数组元素的引用方式:arrayName[index]

  • arrayName数组的变量名称
  • index为数组元素下标,可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
  • 数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 ~ n-1;

备注:每个数组都有一个属性length指明它的长度

代码示例4:

    //创建数组
    int arr[]=new int[4];
    //数组的length属性
    System.out.println(arr.length);

1.1.5 数组元素的初始化

  1. 动态初始化
    数组定义与为数组元素分配空间并赋值的操作分开进行。
    代码示例5:

     int  a[];
     a  = new int[3];
     a[0] = 3;
     a[1] = 9;
     a[2] = 8;
     MyDate dates[]= new MyDate[3];
     dates[0] = new MyDate(22, 7, 1964);
     dates[1] = new MyDate(1, 1, 2000);
     dates[2] = new MyDate(22, 12, 1964);
    
  2. 静态初始化
    在定义数组的同时就为数组元素分配空间并赋值。
    代码示例6:

    int a[] = { 3, 9, 8};
    
    MyDate dates[] = {
    new MyDate(22, 7, 1964),
    new MyDate(1, 1, 2000),
    new MyDate(22, 12, 1964)
    };
    

1.1.6 简单数据类型数组

  1. 简单数据类型数组的定义
    在定义数组的时候,系统会给这个数组分配用于存放这个数组的内存空间
    如图所示:

    int arr[];  
    

    这里写图片描述

  2. 简单数据类型数组的创建
    在创建简单数据类型的数组的时候,系统会分配合适的空间用来存放该种数据类型数据的内存空间,并且将这个数组的各个元素赋一个和数组类型匹配的初值
    如图所示:

    arr=new int[10];
    

这里写图片描述
3. 简单数据类型数组的初始化
对于简单数据类型的数组,当对其进行初始化时,会将对应的值赋给对应的各个数组元素
如图所示:

    arr[0]=1;
    arr[1]=2;
    arr[2]=3;
    ......
    arr[9]=10;

这里写图片描述
基本数据类型数组代码示例

public class Test {
    public static void main(String args[]) {
        //数组声明
        int[] s;
        //创建
        s = new int[10];
        for (int i = 0; i < 10; i++) {
            //初始化
            s[i] = 2 * i + 1;
            //输出结果
            System.out.print(s[i]+",");
        }
    }
}

运行结果:

    1,3,5,7,9,11,13,15,17,19,

备注:分析示例中数组对象在内存中的变化

1.1.7 引用数据类型数组

  1. 引用数据类型数组的定义
    引用类型数组的定义和简单类型数据类型数组的定义并无二致
    如图所示:

    String arr[];
    

    这里写图片描述

  2. 引用数据类型数组的创建
    引用数据类型数组在创建的时候也是首先给数组元素分配内存空间,然后给这些数组元素一个默认的初始值null

    arr=new String[10];
    

    这里写图片描述

  3. 引用数据类型数组的初始化
    在进行引用数据类型数组的初始化的时候,和简单数据类型数组的初始化有些不同,因为数组本身是引用类型,而现在数组元素也是引用类型,所以这个时候需要给数组元素所引用的对象也分配内存空间。

    arr[0]=new String("one");
    arr[1]=new String("two");
    ... ...
    arr[9]=new String("ten");
    

这里写图片描述
数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化

引用数据类型数组代码示例

    /**
    * 自定义日期类
    * 
    * @author change
    *
    */
class MyDate {
    // 月中的天
    private int day;
    // 年中的月
    private int month;
    // 年
    private int year;

    /**
    * 构造函数
    * 
    * @param d
    * @param m
    * @param y
    */
    public MyDate(int d, int m, int y) {
        day = d;
        month = m;
        year = y;
    }

    /**
    * 打印对象信息
    */
    public void display() {
        System.out.println(year + "-" + month + "-" + day);
    }
}

public class TestDemo {
        public static void main(String args[]) {
            //声明数组
            MyDate[] m;
            //创建数组
            m = new MyDate[5];
            for (int i = 0; i < 5; i++) {
                //数组赋值
                m[i] = new MyDate(i + 1, i + 1, 1990 + i);
                //打印对象信息
                m[i].display();
            }
        }
}  

运行结果:

1990-1-1
1991-2-2
1992-3-3
1993-4-4
1994-5-5

备注:分析示例中数组对象在内存中的变化

案例1:解剖数组

public class ArrDemo{
    public static void main(String[] args) {
        //数组的创建
        String arr[]=new String[5];
        //数组的初始化
        arr[0]="唐僧";
        arr[1]="悟空";
        arr[2]="白龙马";
        arr[3]="八戒";
        arr[4]="沙僧";

        //数组的具体的某个元素值
        System.out.println("数组的第四个元素:"+arr[3]);

        //数组的长度属性
         System.out.println("数组的长度是:"+arr.length);

        //遍历数组的第一种方式
        for(int i=0;i<arr.length;i++){
            System.out.print(arr[i]+",");
         }
         //换行 考虑上边的输出去掉最后一个逗号的问题
        System.out.println();

         //遍历数组的方式
        for(int i=0;i<arr.length;i++){
            if(i<arr.length-1){
                System.out.print(arr[i]+",");
            }else{
                System.out.print(arr[i]);
            }
        }
        //换行
        System.out.println();
         //遍历数组的第二种方式
         for(String ar:arr){
            System.out.print(ar+" ");
        }

        //思考第一种方式与第二种方式的区别
    }
}

运行结果:

数组的第四个元素:八戒
数组的长度是:5
唐僧,悟空,白龙马,八戒,沙僧,
唐僧,悟空,白龙马,八戒,沙僧
唐僧 悟空 白龙马 八戒 沙僧   

1.1.8 一维数组的应用

  1. 利用一维数组来进行冒泡排序:

    • 对几个无序的数字进行排序,最常用的方法是所谓的冒泡排序法。这种方法每次比较两个相邻的数,将较小的放到前面,较大的放到后面,这样就可以将这些数中的最大的找出来访到最后,然后比较剩下的数,再在这些数中找出最大的来,直到所有的数字按照从小到大的顺序排列
    • 可以用一个一维数组来存放这些需要进行排序的数字,然后对这个一维数组进行排序
  2. 冒泡排序
    这里写图片描述
    案例2:冒泡排序

    public static void main(String[] args) {
                int[] a={1,3,5,75,1,6,9,8,5,2,2,1,4,2};
                for(int i=0;i<a.length-1;i++){
                          for(int j=i;j<a.length-1;j++){
                                    if(a[i]>a[j+1]){
                                              int temp=a[i];
                                              a[i]=a[j+1];
                                              a[j+1]=temp;
                                    }
                          }
                }
                for(int b:a){
                          System.out.println(b);
                }
      }
    

案例3: 从键盘读入学生成绩,找出最高分,并输出学生成绩等级。
等级说明:

    成绩>=最高分-10    等级为’A’   
    成绩>=最高分-20    等级为’B’
    成绩>=最高分-30    等级为’C’   
    其余                            等级为’D’

提示:先读入学生人数,根据人数创建int数组,存放学生成绩。

/**
 * 此操作都是在正确输入参数的情况测试的!
 * @author change
 *
 */
public class StudentDemo {

    public static void main(String[] args) {
        // 扫描对象
        Scanner scanner = new Scanner(System.in);
        //提示输入录入学生的人数
        System.out.println("请输入录入学生的人数");
        //扫描输入的学生的人数
        String snum = scanner.nextLine();
        //转换成数字
        int num = Integer.parseInt(snum);
        //创建数组
        int arr[]=new int[num];
        System.out.println("请输入"+num+"位学生的成绩");
        //遍历数组
        for(int i=0;i<num;i++){
            //接受输入的学生成绩
            String score = scanner.nextLine();
            //初始化赋值
            arr[i]=Integer.parseInt(score);
        }

        //数组排序
        Arrays.sort(arr);
        //获取最高的成绩5
        int maxScore = arr[num-1];
        System.out.println("学生等级划分结果");
        //遍历数组
        for(int i=0;i<num;i++){
            if(arr[i]>maxScore-10){
                System.out.println(arr[i]+"等级为A");
            }else if(arr[i]>maxScore-20){
                System.out.println(arr[i]+"等级为B");
            }else if(arr[i]>maxScore-30){
                System.out.println(arr[i]+"等级为C");
            }else{
                System.out.println(arr[i]+"等级为D");
            }
        }
    }
}

测试运行:

请输入录入学生的人数
5
请输入5位学生的成绩
98
88
85
75
65
学生等级划分结果
65等级为D
75等级为C
85等级为B
88等级为B
98等级为A

1.1.9 多维数组

Java语言中,多维数组被看作是数组的数组。例如二维数组被看作是一个特殊的一维数组,其每个元素又是一个一维数组。下面主要以二维数组为例进行说明。

  1. 二维数组的声明方式:

     type  var[][] 或 type[][]  var;
     其中type是定义数组包含元素的数据类型,它可以是Java支持的任何一种数据类型,
     可以是基本数据类型,也可以是引用数据类型;
     var是数组的名字,可以是任何一个合法的标识符。  
    
  2. 二维数组举例:

    int a[][] = {{1,2},{3,4,0,9},{5,6,7}};
    

    图解说明如下:
    这里写图片描述

  3. 注意事项

    • Java中二维数组的声明和初始化应按从高维到低维的顺序进行

       int t [][] = new int [4][];
       t[0] = new int[5];
       t[1] = new int[5];
      
       int t1[][] = new int [][4];   //非法
      
    • Java中二维数组不必须是规则矩阵形式

      int[][] tt = new int[4][]; 
      tt[0] = new int[2];
      tt[1] = new int[4];
      tt[2] = new int[6];
      tt[3] = new int[8];
      
      int tt[][] = new int[4][5];
      
  4. 二维数组初始

    • 静态初始化

      int intArray[][] = {{1,2},{2,3},{3,4,5}};
      int intArray1[3][2] = {{1,2},{2,3},{4,5}};  //非法定义
      
    • 动态初始化

      int a[][] = new int[4][5];
      int b[][] = new int[3][] 
      b[0] = new int[2];
      b[1] = new int[3];
      b[2] = new int[5];
      
  5. 二维数组示例:

    public class ArrsDemo {
    
        public static void main(String[] args) {
            //创建二维数组
            int[][] a = new int[3][];
            //遍历数组
             for (int i = 0; i < a.length; i++) {
               // 创建一维数组的数组元素
                a[i] = new int[i + 2];
               //元素赋值 
                for (int j = 0; j < a[i].length; j++) {
                    a[i][j] = i + j;
                }
             }
            // 打印
             for (int i = 0; i < a.length; i++) {// 代表的是行
                 for (int j = 0; j < a[i].length; j++) {// 代表行中的元素:即就是几列。
                    System.out.print(a[i][j] + " ");
                 }
                System.out.println();
             }
    
        }
    }
    

    运行结果:

    0 1 
    1 2 3 
    2 3 4 5 
    

案例4:使用二维数组打印一个 10 行杨辉三角
分析:
这里写图片描述

  • 第一行有 1 个元素, 第 n 行有 n 个元素
  • 每一行的第一个元素和最后一个元素都是 1
  • 从第三行开始, 对于非第一个元素和最后一个元素的元素,其它元素的值的公式如下:

        yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];   
    

代码实现:

/*杨辉三角实现*/
public class YangHuiDemo {

    public static void main(String[] args) {
        //定义二维数组
        int yanghui[][] =new int[10][];

        //遍历二维数组
        for(int row=0;row<yanghui.length;row++){
            //每行有n个元素
            yanghui[row]=new int[row+1]; //第一行有一个元素,依次类推.
            //位每行元素赋值
            for(int col=0;col<yanghui[row].length;col++){
                if(row==col||col==0){
                    yanghui[row][col]=1;
                }
                else{
                  yanghui[row][col]=yanghui[row-1][col-1]+yanghui[row-1][col];
                }
                //遍历元素  \t水平制表符
                System.out.print(yanghui[row][col]+"\t");
            }
            System.out.println();
        }
    }
}   

运行结果:

1   
1   1   
1   2   1   
1   3   3   1   
1   4   6   4   1   
1   5   10  10  5   1   
1   6   15  20  15  6   1   
1   7   21  35  35  21  7   1   
1   8   28  56  70  56  28  8   1   
1   9   36  84  126 126 84  36  9   1   

扩展思考:怎么实现规则的输出?

1.1.10 数组中常见的异常

  1. 数组脚标越界异常(ArrayIndexOutOfBoundsException)
    示例代码:

    public class ArrException {
    
        public static void main(String[] args) {
            int[] arr = new int[2];
            System.out.println(arr[2]);
        }
    }
    

    运行结果:

    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
    at arr.ArrException.main(ArrException.java:7)
    
  2. 空指针异常(NullPointerException)
    示例代码:

    public class ArrException {
    
        public static void main(String[] args) {
            String[] arr = null;
            System.out.println(arr[0]);
        }
    }
    

    运行结果:

    Exception in thread "main" java.lang.NullPointerException
    at arr.ArrException.main(ArrException.java:7)
    

1.2 反射

1.2.1 反射介绍

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制

简单的说:一个类有多个组成部分,例如:成员变量,方法,构造方法等。反射就是加载类,并解剖出类的各个组成部分

Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;生成动态代理

备注:程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言

1.2.2 反射的API介绍

Java反射所需要的类并不多,主要有java.lang.Class类和java.lang.reflect包中的Field、Constructor、Method、Array类。
注意:Class类是Java反射的起源,针对任何一个你想探勘的类,只有先为它产生一个Class类的对象,接下来才能通过Class对象获取其他想要的信息。

  1. Class类
    • JVM为每种类型管理着一个独一无二的Class对象—每个类(型)都有一个Class对象
    • Java程序运行过程中,当需要创建某个类的实例时,JVM首先检查所要加载的类对应的Class对象是否已经存在。如果还不存在,JVM就会根据类名查找对应的字节码文件并加载,接着创建对应的Class对象,最后才创建出这个类的实例。
    • Java的基本数据类型都对应着一个Class对象;关键字void也都对应着一个Class对象;每个数组属性也被映射为 Class 对象,所有具有相同类型和维数的数组都共享该 Class 对象
      因此,运行中的类或接口在JVM中都会有一个对应的Class对象存在,它保存了对应类或接口的类型信息。要想获取类或接口的相应信息,需要先获取这个Class对象。
  2. 加载类
    • Java中有一个Class类用于代表某一个类的字节码
    • Class类即然代表某个类的字节码,它当然就要提供加载某个类字节码的方法:forName()。forName方法用于加载某个类的字节码到内存中,并使用class对象进行封装
    • 另外两种得到class对象的方式
      类名.class: Manager.class; int.class; double[].class;
      对象.getClass()
  3. 解剖类

    • Class对象提供了如下常用方法

      Public  Constructor  getConstructor(Class<?>... parameterTypes)
      Public  Method  getMethod(String name, Class<?>... parameterTypes) 
      Public  Field  getField(String name)   public
      public Constructor getDeclaredConstructor(Class... parameterTypes)
      public Method getDeclaredMethod(String name,Class... parameterTypes)
      public Field getDeclaredField(String name)  
      

      这些方法分别用于从类中解剖出构造函数、方法和成员变量(属性)。解剖出的成员分别使用Constructor、 Method 、 Field 对象表示。

1.2.3 反射案例

**案例5:**Java反射案例

class Person {
      private int    age;
      private String name;

      public Person() {

      }

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

      public int getAge() {
                return age;
      }

      public void setAge(int age) {
                this.age = age;
      }

      public String getName() {
                return name;
      }

      public void setName(String name) {
                this.name = name;
      }
}

interface ActionInterface {
      public void walk(int m);
}   

class SuperMan extends Person implements ActionInterface {
      private boolean blueBriefs;

      public void fly() {
                System.out.println("超人会飞耶~~");
      }

      public boolean isBlueBriefs() {
                return blueBriefs;
      }

      public void setBlueBriefs(boolean blueBriefs) {
                this.blueBriefs = blueBriefs;
      }

      @Override
      public void walk(int m) {
                System.out.println("超人会走耶~~走了" + m + "米就走不动了!");
      }
}

示例1:通过Java反射机制得到类的包名和类名

public static void main(String[] args){
    Person person = new Person();
    System.out.println("包名:" + person.getClass().getPackage().getName());
    System.out.println("完整类名:" + person.getClass().getName());
}

示例2:验证所有的类都是Class类的实例对象

public static void main(String[] args){
    // 定义两个类型都未知的Class,设置初值为null,看看如何给它们赋值成Person类
    Class<?> class1 = null;
    Class<?> class2 = null;
    // 写法1,可能抛出 ClassNotFoundException 异常,多用这个写法
    try {
            class1 = Class.forName("shuang.com.demo.Person");//包名要写正确,不然抛异常
            System.out.println("写法1,包名:" + class1.getPackage().getName() + " , 完整类名:" + class1.getName());
    } catch (ClassNotFoundException e) {
            System.out.println("写法1找不到类");
    }
    // 写法2
    class2 = Person.class;
    System.out.println("写法2,包名:" + class2.getPackage().getName() + " , 完整类名:" + class2.getName());
 }

示例3:通过Java反射机制,用 Class 创建类对象,这也就是反射存在的意义所在

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
    Class<?> class1 = null;
    class1 = Class.forName("shuang.com.demo.Person");
    // 由于这里不能带参数,所以你要实例化的这个类Person,一定要有无参构造函数
    Person person = (Person) class1.newInstance();
    person.setName("xiaoming");
    person.setAge(20);
    System.out.println(person.getName() + " , " + person.getAge());
}

示例4:通过Java反射机制得到一个类的构造函数,并实现创建带参实例对象

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException,IllegalArgumentException, InvocationTargetException{
    Class<?> class1 = null;
    Person person1 = null;
    Person person2 = null;
    class1 = Class.forName("shuang.com.demo.Person");
    Constructor<?>[] constructors = class1.getConstructors();
    person1 = (Person) constructors[0].newInstance();
    person1.setName("xiaoming");
    person1.setAge(20);
    System.out.println(person1.getName() + " , " + person1.getAge());
    person2 = (Person) constructors[1].newInstance(21, "xiaohong");
    System.out.println(person2.getName() + " , " + person2.getAge());
}

示例5:通过Java反射机制操作成员变量, set 和 get

public static void main(String args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, SecurityException{
    Class<?> class1 = Class.forName("shuang.com.demo.Person");
    Object obj = class1.newInstance();
    Field personNameField = class1.getDeclaredField("name");
    personNameField.setAccessible(true); // 取消访问检查
    personNameField.set(obj, "小虎");
    System.out.println("修改属性之后得到属性变量的值:" + personNameField.get(obj));
}

示例6:通过Java反射机制得到类的一些属性:继承的接口、父类、函数信息、成员信息、类型等

public static void main(String[] args) throws ClassNotFoundException{
    Class<?> class1 = Class.forName("shuang.com.demo.SuperMan");

    // 取得父类名称
    Class<?> superclass = class1.getSuperclass();
    System.out.println("SuperMan类的父类名:" + superclass.getName());

    Field[] fields = class1.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
              System.out.println("类中的成员" + i + ": " + fields[i]);
    }

    // 取得类方法
    Method[] methods = class1.getDeclaredMethods();
    for (int i = 0; i < methods.length; i++) {
              System.out.println("取得SuperMan类的方法" + i + ":");
              System.out.println("函数名:" + methods[i].getName());
              System.out.println("函数返回类型:" + methods[i].getReturnType());
              System.out.println("函数访问修饰符:" + Modifier.toString(methods[i].getModifiers()));
              System.out.println("函数代码写法: " + methods[i]);
    }

    // 取得类实现的接口,因为接口类也属于Class,所以得到接口中的方法也是一样的方法得到哈
    Class<?> interfaces[] = class1.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
              System.out.println("实现的接口类名: " + interfaces[i].getName());
    }
}

示例7:通过Java反射机制调用类方法

public static void main(String[] args)throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,InstantiationException, InvocationTargetException{
    Class<?> class1 = Class.forName("shuang.com.demo.SuperMan");

    System.out.println("调用无参方法fly():");
    Method method = class1.getMethod("fly");
    method.invoke(class1.newInstance());

    System.out.println("调用有参方法walk(int m):");
    method = class1.getMethod("walk", int.class);
    method.invoke(class1.newInstance(), 100);
}

5 总结

  1. 数组的声明及使用
  2. 了解反射机制

6 预习任务

  1. Object类
  2. String类
  3. StringBuffer与StringBuilder类
  4. 日期处理的类
  5. Math与Random

7 参考资料

以上都是个人整理资料部分,有问题欢迎大家留言!如需转载,请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值