Javaの异常、断言、泛型、日志

1.异常

  1. 什么是异常

    • 异常,就是不正常的意思。指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。注意:异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.
  2. 异常体系

    • 异常机制其实是帮助我们找到程序中的问题,异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。

    • Throwable体系:

      Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。

      Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒、阑尾炎。

    • Throwable中的常用方法:

      public void printStackTrace() :打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

      public String getMessage() :获取发生异常的原因。提示给用户的时候,就提示错误原因。

      public String toString() :获取异常的类型和异常描述信息(不用)。

  3. 异常分类

    • 我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。

    • 异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?

    • 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常);

    • 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)。

    • 常见异常:

  4. 异常处理

    1. 抛出异常throw
      在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?
      1. 创建一个异常对象。封装一些提示信息(信息可以自己编写)。
      2. 需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。

代码演示:

public class ThrowDemo {
    public static void main(String[] args) {
        //创建一个数组
        int[] arr = {2,4,52,2};
        //根据索引找对应的元素
        int index = 4;
        int element = getElement(arr, index);
        System.out.println(element);
        System.out.println("over");
    }
    /*
     * 根据 索引找到数组中对应的元素
     */
    public static int getElement(int[] arr,int index){
       //判断  索引是否越界 
        if(index<0 || index>arr.length‐1){
             /*
             判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
             这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
              */
             throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
        }
        int element = arr[index];
        return element;
    }
}

如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。
那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续讲问题声明出去,使用throws声明处理。

②声明异常throws
声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
声明异常格式: 修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
代码演示:

public class ThrowsDemo2 {
    public static void main(String[] args) throws IOException {
        read("a.txt");
    }
    public static void read(String path)throws FileNotFoundException, IOException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
        if (!path.equals("b.txt")) {
            throw new IOException();
        }
    }
}

③捕获异常try…catch

捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

捕获异常语法如下:

    try{
         //编写可能会出现异常的代码
    }catch(异常类型  e){
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }

代码演示:

public class TryCatchDemo {
    public static void main(String[] args) {
        try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。
            read("b.txt");
        } catch (FileNotFoundException e) {// 括号中需要定义什么呢?
           //try中抛出的是什么异常,在括号中就定义什么异常类型  
            System.out.println(e);
        }
        System.out.println("over");
    }
    /*
     *
     * 我们 当前的这个方法中 有异常  有编译期异常
     */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
    }
}

④多个异常捕获

try{
     //编写可能会出现异常的代码
}catch(异常类型A  e){try中出现A类型异常,就用该catch来捕获.
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}catch(异常类型B  e){try中出现B类型异常,就用该catch来捕获.
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}

多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

⑤finally 代码块finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。finally不能单独使用,必须和try配套使用。只有在try或者catch中调用退出JVM的相关方法时,finally才不会执行,否则finally绝对会执行。

  5. 自定义异常1. 自定义一个编译期异常: 自定义类 并继承于 java.lang.Exception 。2. 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException 。  
     * 演示
// 业务逻辑异常
public class LoginException extends Exception {
    /**
     * 空参构造
     */
    public LoginException() {
    }
    /**
     *
     * @param message 表示异常提示
     */
    public LoginException(String message) {
        super(message);
    }
}
 
public class Demo {
    // 模拟数据库中已存在账号
    private static String[] names = {"bill","hill","jill"};
   
    public static void main(String[] args) {    
        //调用方法
        try{
              // 可能出现异常的代码
            checkUsername("nill");
            System.out.println("注册成功");//如果没有异常就是注册成功
        }catch(LoginException e){
            //处理异常
            e.printStackTrace();
        }
    }
    //判断当前注册账号是否存在
    //因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkUsername(String uname) throws LoginException{
        for (String name : names) {
            if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
                throw new LoginException("亲"+name+"已经被注册了!");
            }
        }
        return true;
    }
}

2. 断言

  1. 断言概述编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设。可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真。 
  2. 断言使用格式
     1.assert 布尔表达式 
     2.assert 布尔表达式:消息 
     使用第一种格式,当布尔类型表达式为false时,抛出AssertionError异常;如果是第二种格式,则输出错误消息。
public class Test {
    public static void main(String[] args) {
        boolean isOk = 1 > 2;
        //assert isOk;
        assert isOk:"1不该大于2";
        System.out.println("程序正常");
    }
}
   3. 断言的启用
        在默认情况下idea中的断言不起作用,在idea等工具中可以开启和关闭断言功能:Run→Edit Configrations->在VM栏里输入-ea开启断言,输入-da关闭断言。

3.日志Logger

3.1 Logger
在Java中实现日志记录的方式有很多种,其中最简单的方式,就是System.out.print,System.err.print 这样直接在控制台打印消息了。接下来我们介绍的:java.util.logging.Logger是在JDK 1.4 版本之后加入的,提供了日志记录的API ,可以往控制台/文件中写日志了。

Logger 对象用来记录特定系统或应用程序组件的日志消息。一般使用圆点分隔的层次名称空间来命名 Logger。Logger 名称可以是任意的字符串,但是它们一般应该基于被记录组件的包名或类名,如 java.net 或 javax.swing。此外,可以创建“匿名”的 Logger,其名称未存储在 Logger 名称空间中。

可通过调用某个 getLogger 工厂方法来获得 Logger 对象。这些方法要么创建一个新 Logger,要么返回一个合适的现有 Logger。

日志消息被转发到已注册的 Handler 对象,该对象可以将消息转发到各种目的地,包括控制台、文件、OS 日志等等。

3.2 创建Logger对象
static Logger getLogger(String name) 为指定子系统查找或创建一个 logger。

Logger logger1 = Logger.getLogger(“com.suncaper.lxp.LogDemo”);

注意:name是Logger的名称,当名称相同时候,同一个名称的Logger只创建一个。

3.3 Logger的级别

类型 解释
SEVERE 严重
WARNING 警告
INFO 信息
CONFIG 配置
FINE 良好
FINER 较好
FINEST 最好
ALL 开启所有级别的日志记录
OFF 关闭所有级别的日志记录

logger默认的级别是INFO,比INFO更低的日志将不显示。

Logger的默认级别定义是在jre安装目录的lib下面的logging.properties。

可通过setLevel(Level.FINEST)方法改变级别。

public class Test {
    public static void main(String[] args) throws IOException {
        Logger log = Logger.getLogger("com");
        log.setLevel(Level.FINEST);
    }
}

3.4 Handler
Handler 对象从 Logger 中获取日志信息,并将这些信息导出。例如,它可将这些信息写入控制台或文件中,也可以将这些信息发送到网络日志服务中,或将其转发到操作系统日志中。可通过执行 setLevel(Level.OFF) 来禁用 Handler,并可通过执行适当级别的 setLevel 来重新启用。

//将日志打印至控制台
public class Test {
    public static void main(String[] args) throws IOException {
        Logger logger = Logger.getLogger("com.suncaper.test.LogDemo");
        logger.setLevel(Level.ALL);
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setLevel(Level.FINEST);
        logger.addHandler(consoleHandler);
 
        logger.severe("严重");
        logger.warning("警告");
        logger.info("信息");
        logger.config("配置");
        logger.fine("良好");
        logger.finer("较好");
        logger.finest("最好");
    }
}
 
//将日志写入文本
public class Test {
    public static void main(String[] args) throws IOException {
        Logger log = Logger.getLogger("com");
        log.setLevel(Level.INFO);
        Logger log1 = Logger.getLogger("com.suncaper");
        ConsoleHandler consoleHandler =new ConsoleHandler();
        consoleHandler.setLevel(Level.ALL);
        log.addHandler(consoleHandler);
        FileHandler fileHandler = new FileHandler("d://testlog.log");
        fileHandler.setLevel(Level.INFO);
        log.addHandler(fileHandler);
        log.info("111");
        log1.info("222");
        log1.fine("333");
    }
}

默认的日志方式是xml格式,很烂。所以最好自定义下logger的格式。需要用Formatter来定义。

3.5 Formatter
Formatter 为格式化 LogRecords 提供支持。

一般来说,每个日志记录 Handler 都有关联的 Formatter。Formatter 接受 LogRecord,并将它转换为一个字符串。默认设置即为:fileHandler.setFormatter(new XMLFormatter());可通过fileHandler.setFormatter(new SimpleFormatter())来修改日志记录的信息。

//将日志格式化写入文本
public class Test {
    public static void main(String[] args) throws IOException {
        Logger log = Logger.getLogger("com");
        log.setLevel(Level.INFO);
        Logger log1 = Logger.getLogger("com.suncaper");
        ConsoleHandler consoleHandler =new ConsoleHandler();
        consoleHandler.setLevel(Level.ALL);
        log.addHandler(consoleHandler);
        FileHandler fileHandler = new FileHandler("d://testlog.log");
        fileHandler.setFormatter(new SimpleFormatter());
        fileHandler.setLevel(Level.INFO);
        log.addHandler(fileHandler);
        log.info("111");
        log1.info("222");
        log1.fine("333");
    }
}

4.泛型

  1. 泛型概述
    • 泛型:就是一种不确定的数据类型。
  2. 反省的使用
    1. 定义和使用含有泛型的类
      • 格式:修饰符calss 类名<代表泛型的变量> {}
    2. 含有泛型的方法
      • 格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
      • 演示:
public class Person<T> {//定义泛型类
    private T t;
 
    public T getT() {
        return t;
    }
 
    public void setT(T t) {
        this.t = t;
    }
    public  <K> K hello(K k){//泛型方法1
        return k;
    }
    public  <K> void hello1(K k){//泛型方法2
        System.out.println("泛型方法");
    }
}
 
 
public class Test {
    public static void main(String[] args) {
        Person<String> p=new Person<>();
        p.setT("hello");
        System.out.println(p.getT());
        p.hello("hello");//调用方法时,确定泛型的类型
        p.hello(11);
    }
}
3. 含有泛型的接口
    格式:修饰符 interface 接口名 <代表泛型的变量>{}
    1.定义子类时确定泛型的类型
public interface A<T> {//定义泛型接口A
    void add(T t);
    void del(T t);
 
}
 
public class B implements A<String> {//定义子类B实现泛型接口A
    @Override
    public void add(String s) {
 
    }
 
    @Override
    public void del(String s) {
 
    }
}
 
public class Test {//测试类
    public static void main(String[] args) {
        B b=new B();
        b.add("hello");
    }
}

2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

public interface A<T> {//定义泛型接口A
    void add(T t);
    void del(T t);
 
}
 
public class B<T> implements A<T> {
    @Override
    public void add(T t) {
 
    }
 
    @Override
    public void del(T t) {
 
    }
}
 
public class Test {//测试类
    public static void main(String[] args) {
        B b=new B<String>();
        b.add("hello");
    }
}
  1. 泛型通配符
    • 当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。
public class Person<T> {
    private T t;
 
    public T getT() {
        return t;
    }
 
    public  void setT(T t) {
        this.t = t;
    }
}
 
 
 
public class Test {
    public static void main(String[] args) {
        Person<String> p=new Person<>();
        Person<Integer> p1=new Person<>();
        ff2(p);
        ff2(p1);
 
    }
    public static void  ff2(Person<?> person){
 
    }
}

在泛型中可以指定一个泛型的上限和下限。

泛型的上限:

格式: 类型名称 <? extends 类 > 对象名称

意义: 只能接收该类型及其子类

public class Person<T> {
    private T t;
 
    public T getT() {
        return t;
    }
 
    public  void setT(T t) {
        this.t = t;
    }
}
 
 
 
public class Test {
    public static void main(String[] args) {
        Person<String> p=new Person<>();
        Person<Integer> p1=new Person<>();
        ff1(p);
        ff1(p1);//报错
 
    }
     public static void  ff1(Person<? extends  String> person){
 
    }
}
泛型的下限:

格式: 类型名称 <? super 类 > 对象名称

意义: 只能接收该类型及其父类型
public class Person<T> {
    private T t;
 
    public T getT() {
        return t;
    }
 
    public  void setT(T t) {
        this.t = t;
    }
}
 
 
 
public class Test {
    public static void main(String[] args) {
        Person<String> p=new Person<>();
        Person<Integer> p1=new Person<>();
        ff(p);
        ff(p1);//报错
 
    }
     public static void  ff(Person<? super  String> person){
 
    }
}

1.编写一个泛型方法,实现任意引用类型数组指定位置元素交换。

public static <T> T[]  exchange(T[] a,int i,int j){
        T temp = a[i];
        a[i] = a[j];
        a[j] = temp;
        return a;
    }

2.编写一个泛型方法,接收一个任意引用类型的数组,并反转数组中的所有元素。

public static <T> T[] turnOver(T[] a){
        T temp;
        for(int i = 0,j = a.length-1;i<j;i++,j--){
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        return a;
    }

3.定义方法,遍历传递过来的任意引用类型数组。

public static <T> void traversal(T[] a){
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i] + " ");
        }
        System.out.println();
    }

练习一:异常的体系
问题:

  1. 请描述异常的继承体系
    • Throwable类是所有异常类的根类,所有的异常类都是由它继承
      Exception类是程序员可以根据问题描述可以处理的
      在Exception类中可以分为运行时异常和编译时异常,
      编译时异常必须处理否则无法运行,
      运行时异常就是编译通过,在运行时发生的异常
      运行时异常在发生时会退出当前的方法
  2. 请描述你对错误(Error)的理解
    • Error:是严重错误,无法通过处理的错误,只能事先避免,不然的话程序无法运行
      从Java设计的角度来讲,程序基本不可以通过后续代码修复,从而理应终止。当然,
      从语法上来讲,所有这些都可以被写进catch里面,但是Error因为上述原因,不应该被代码处理。
  3. 请描述你对异常(Expection的理解)
    • 从本质上来讲,java异常是一个java对象(继承Object),和一般的java对象一样,封装了一些成员变量和操作,我们可以向操作一般java对象那样去操作java异常对象(我还特地去试了试可不可以使用throw关键字操作一般的java对象,发现编译错误,证明它仅仅是操作异常类的语法)
    • 从代表的含义上来说:java异常是一种错误情况,是程序不希望出现的现象,但是由于程序本身的设计逻辑和运行的环境等因素,出现异常的情况不可避免,java虚拟机针对不同的异常情况定义了很多异常类,当jvm运行程序发现对应的异常时,将会向外面抛出异常
  4. 请描述你对运行时异常(RuntimeException)的理解
    • 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会编译器检测(不报错)。(如数学异常)。
      该异常在编译通过之后运行程序时产生,运行时异常是不需要捕获的,程序员可以不去处理,当异常出现时,虚拟机会处理。常见的运行时异常有空指针异常。

练习二:throw与throws的区别
问题:
请描述throw的使用位置,作用是什么?
* throw的使用位置是在方法体内部,用于抛出异常。当方法
在执行过程中遇到异常情况时,将异常信息封装为异常对象,
然后throw

请描述throws的使用位置,作用是什么?
* throws出现在方法的声明中,表示该方法可能能会抛出的异
常,允许throws后面跟着多个异常类型
练习三:异常的处理方式
问题:

  1. 异常处理方式有几种,分别是什么?
    共有五种:

    • 抛出异常throw,在方法体内部使用throw关键字抛出指定的异常对象

    • 声明异常throws

      • 在方法头部使用throws告知编译器此方法可能会抛出的异常
      • 如果方法内通过throw抛出了编译时异常,而没有捕获处理
        ,那么必须通过throws进行声明,让调用者去处理。
      • 声明异常格式: 修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
    • 捕获异常try…catch

      • 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
    • 多个异常捕获

      • 使用多个try…catch对异常进行捕获
    • finally代码块

      • 有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些
        语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
        finally不能单独使用,必须和try配套使用。
      • 只有在try或者catch中调用退出JVM的相关方法时,finally才不会执行,否则finally绝对会执行。
  2. 详细阐述每种方式对异常是如何处理的

    • throw方式:
      • 如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是
        将问题返回给该方法的调用者。
      • 先封装一些提示信息 再将异常告知程序的使用者

    例:

public class ThrowDemo {
    public static void main(String[] args) {
        //创建一个数组
        int[] arr = {2,4,52,2};
        //根据索引找对应的元素
        int index = 4;
        int element = getElement(arr, index);
        System.out.println(element);
        System.out.println("over");
    }
    /*
     * 根据 索引找到数组中对应的元素
     */
    public static int getElement(int[] arr,int index){
       //判断  索引是否越界
        if(index<0 || index>arr.length‐1){
             /*
             判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
             这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
              */
             throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
        }
        int element = arr[index];
        return element;
    }
}
  • throws方式
public class ThrowsDemo2 {
    public static void main(String[] args) throws IOException {
        read("a.txt");
    }
    public static void read(String path)throws FileNotFoundException, IOException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
        if (!path.equals("b.txt")) {
            throw new IOException();
        }
    }
}
  • 捕获异常语法如下:
try{
     //编写可能会出现异常的代码
}catch(异常类型  e){
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}

例:

public class TryCatchDemo {
    public static void main(String[] args) {
        try {// 当产生异常时,必须有处理方式。要么捕获,要么声明。
            read("b.txt");
        } catch (FileNotFoundException e) {// 括号中需要定义什么呢?
           //try中抛出的是什么异常,在括号中就定义什么异常类型  
            System.out.println(e);
        }
        System.out.println("over");
    }
    /*
     *
     * 我们 当前的这个方法中 有异常  有编译期异常
     */
    public static void read(String path) throws FileNotFoundException {
        if (!path.equals("a.txt")) {//如果不是 a.txt这个文件
            // 我假设  如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常  throw
            throw new FileNotFoundException("文件不存在");
        }
    }
}
  • 多个异常捕获实例
try{
     //编写可能会出现异常的代码
}catch(异常类型A  e){  当try中出现A类型异常,就用该catch来捕获.
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}catch(异常类型B  e){  当try中出现B类型异常,就用该catch来捕获.
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}finally{
    //最后执行的代码
}

练习四:常见异常,及产生原因
问题:请列举常见异常7个,并说明产生原因。
答:

  1. java.lang.nullpointerexception
          
     空指针异常 简单地说就是调用了未经过初始化的对象或
     者是不存在的对象,常出现在创建图片,调用数组这些操作中。
  2. java.lang.arithmeticexception
          
     这个异常就是数学运算异常,比如程序中出现了除以0的
     这种运算
  3. java.lang.arrayindexoutofboundsexception  
     这个异常就是数组下标越界,在调用数组时使用了比数
     组本身大小更大的下标。
  4. SQLException
     操作数据库异常,这个异常是因为操作数据库异常,一般是数据库的操作不当导致的
  5. FileNotFoundException
     文件未找到异常,一般发生于文件操作时找不到目标路径的文件
  6. java.lang.AbstractMethodError
     抽象方法错误。当应用试图调用抽象方法时抛出。
  7. java.lang.AssertionError
     断言错。用来指示一个断言失败的情况

具体请见https://blog.csdn.net/yangzhengjianglove/article/details/81233784

练习五:自定义异常类
问题:
请使用代码实现每一个学生(Student)都有学号,姓名和分数,分数永远不能为负数如果老师给学生赋值一个负数,抛出一个自定异常

public class Demo5 {
    static Scanner sc = new Scanner(System.in);
    //请使用代码实现
    //每一个学生(Student)都有学号,姓名和分数,分数永远不能为负数
    //如果老师给学生赋值一个负数,抛出一个自定异常
    public static void main(String[] args) {
        Student student = new Student();
        try {
            checkStudentScore(student);
        } catch (MyFirstException e) {
            e.printStackTrace();
        }
    }
    public static boolean checkStudentScore(Student student) throws MyFirstException{
        System.out.print("请输入学生的姓名");
        student.setName(sc.next());
        System.out.println("请输入学生的学号");
        student.setId(sc.next());
        System.out.println("请输入学生的分数");
        student.setScore(sc.nextInt());
        if(student.getScore()<0){
            throw new MyFirstException("分数能为负值吗?憨憨?");
        }
        return true;
    }
}
public class Student {
    private String id;
    private String name;
    private int score;

    public Student() {
    }

    public Student(String id, String name, int score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }
}