05 JavaSE-- Exception(两种类型、三种处理方式)、Generic

Exception 异常

  • 异常也是对象,也有自己的体系,在这个体系中,所有异常对象的根类是 throwable 接口。
  • 异常和 error 错误是不同的概念。
    错误是严重的 JVM 系统问题,一般不期待程序员去捕获、处理这些错误,同时,也不应期待程序不会从错误中恢复。
    当发生错误时,通常意味着应用程序已经无法正常执行,最好的做法是记录错误信息然后终止程序。
    在这里插入图片描述
  • Java 提供了以下关键字和类来支持异常处理:
    • try:用于包裹可能会抛出异常的代码块。
    • catch:用于捕获异常并处理异常的代码块。
    • finally:用于包含无论是否发生异常都需要执行的代码块。
    • throw:用于手动抛出异常。
    • throws:用于在方法声明中指定方法可能抛出的异常。
    • Exception 类:是所有异常类的父类,它提供了一些方法来获取异常信息,如 getMessage()、printStackTrace() 等。
  • 在 Java 中,异常分为两种:
    • Checked Exceptions 检查(强制)性异常:必须提前在代码中处理的异常,如果不处理编译器报错。
    • Unchecked / RunTime Exceptions 非检查性(运行时)异常:可以在代码中选择性处理的异常,即使不处理也能不报错编译通过

1. Checked Exceptions 检查性/强制性/编译时异常

  • 强制性异常是由编译器检查到的异常
  • 强制性异常必须提前在程序中写好代码处理。要么通过 try-catch语句捕获,要么通过方法签名声明抛出。
  • 强制性异常通常是由外部因素引起的,例如文件不存在、网络连接中断等。
  • 强制性异常必须在代码中显式处理,要么通过 try-catch 块捕获并处理,要么通过在方法签名中使用 throws 关键字声明该异常,以通知调用者处理。
try {
    // 可能会抛出 IOException 的代码
} catch (IOException e) {
    // 处理 IOException
}
public void readFile() throws IOException {
    // 可能会抛出IOException的代码
}

2. Unchecked / RunTime Exceptions 非检查性(运行时)异常

  • 运行时异常是在运行时抛出的异常
  • 运行时异常无需提前在程序中设置捕获语句进行处理。
  • 运行时异常通常是由程序内部错误引起的,例如空指针引用、数组越界等。
  • 运行时异常通常是由程序员在编写代码时可以避免的,因此也称为“非强制性”异常。

3. 处理异常

  • 异常的处理包括两种方式:
    • 声明异常:类似于推卸责任的处理方式
      在方法定义时使用 throws 关键字声明异常,告知调用者,调用这个方法可能会出现异常,但自身并不处理。这种处理方式的态度是:如果出现了异常则会抛给调用者来处理。
    • 捕获异常:真正的处理
      在可能出现异常的代码上使用 try-catch 进行捕获处理。这种处理方式的态度是:把异常抓住。

4. 手动抛出异常 throw

  • 程序在运行中检测到错误后,可以创建一个合适类型的异常实例并抛出它
  • 在同一方法内先用 throw 抛出异常,再用 try-catch 捕获没有任何作用。正确的做法是调用该方法的方法内,用 try-catch 捕获
  • 手动抛出的异常有两种, 分别为运行时异常和编译时异常.、
    • 抛出运行时异常时,可以不用处理抛出的这个异常.
    • 抛出编译时异常时,必须要处理抛出的这个异常,否则编译不能通过
/** divide 方法内部检查了除数是否为 0,如果是,则使用 throw 关键字抛出一个 ArithmeticException。
 - 调用这个方法的代码块通过 try-catch 捕获了这个异常,并在 catch 块中处理了这个错误情况
 */ 
 public static double divide(double chushu, double beichushu) throws ArithmeticException {
 // 事实上,即使不手动抛出,JVM 也会检测到该运行时异常,自动抛出
 // 因此下面三行代码有没有,输出结果都一样
        if (beichushu == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return beichushu / chushu;
    }

    public static void main(String[] args) {
        try {
            double result = divide(10, 0);
            System.out.println("结果是: " + result);
        } catch (ArithmeticException e) {
            System.err.println("发生错误: " + e.getMessage());
        }
    }

在这里插入图片描述

public class ThrowEx {

    // 手动抛出运行时异常,可以不处理
    public void setAge(int age) {
        if (age < 0) {
            throw new NullPointerException("输入的年龄小于0");
        }
    }
    
    // 手动抛出编译时异常,调用该方法者必须要处理这个异常
    public void setAge2(int age) throws FileNotFoundException {
        if (age < 0) {
            throw new FileNotFoundException("输入的年龄小于0");
        }
    }

    public static void main(String[] args) {
        ThrowEx throwEx = new ThrowEx();

// 首先明确,尽管方法 1, 2 都抛出了异常,但在调用方法种,并没有处理抛出的异常

		//方法 1 指定的异常是运行时异常,调用者不是必须处理这个异常,编译也能通过
		//方法 2 指定的异常是编译时异常,由于调用者没有处理,因此无法编译通过
        throwEx.setAge(-5);
        //throwEx.setAge2(-5);
    }

调用方法 1 时,正常编译构建,并送出错误提示
在这里插入图片描述
调用方法 2 时,无法通过编译构建
在这里插入图片描述
同时调用方法1,2时,依然无法通过编译构建,这验证了之前说过的话:抛出编译时异常时,必须要处理抛出的这个异常,否则编译不能通过。

5. 自动捕获异常 try-catch-finally

  • 使用 try-catch-finally 处理编译时异常,是让程序在编译时就不再报错,但是运行时仍然有可能报错。相当于我们使用 try-catch 将一个编译时可能出现的异常,延迟到运行时出现。
  • 在开发中,运行时异常比较常见,此时一般不用 try-catch 去处理,因为处理和不处理都是一个报错,最好办法是去修改代码。
  • try 和 catch 必须配对使用,finally 则没有强制要求
  • try 语句块中:放的是可能出现问题的代码,尽量不要把不相关的代码放入到里面,否则会出现截断的问题。
  • catch 括号中,声明想要捕获的异常类型;语句块中,放置捕获指定异常后处理问题的代码,如果问题在 try 语句块中没有出现,则 catch 不会运行。
  • finally 语句块中:放的是不管问题异常是否产生,都要执行的代码。
/**Integer.parseInt(str)方法将 str 转换为整数。但由于str的内容不是有效的整数(它包含字母而非数字),
 - 这个操作将会抛出一个 NumberFormatException。
 - catch 括号中声明本次捕获目标是 NumberFormatException,如果发生此异常,将执行该块中的代码,即 e.printStackTrace()。
 - 第二个 catch 括号中声明本次捕获目标是 Exception,由于 Exception 本就是所有异常的根类
 - 因此第二个 catch 可以捕获除 NumberFormatException 之外的所有异常类型(前提是这些异常没有在第一个 catch 块中被捕获)。
 - 这里,如果捕获到任何其他异常,程序将输出e.getMessage(),即异常的描述信息。
 - finally块:无论是否发生异常,finally块中的代码都会被执行。
 */
 public static void main(String[] args) {
        String str = "avocado";

        try {
            int i = Integer.parseInt(str);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("运行完毕");
        }
    }

在这里插入图片描述

6. 声明异常 throws

  • 在 Java 中,当前执行的语句必属于某个方法。
  • 每个方法都必须声明它可能抛出的强制性异常的类型。
  • 声明的方式是:在方法头使用 throws 关键字
public void myMethod throws Exceptionl, Exception2,… ,ExceptionN
  • throws 一般用于方法中可能存在异常时,需要将异常情况向方法之上的层级抛出,由抛出方法的上一级来解决这个问题,如果方法的上一级无法解决的会就会再将异常向上抛出,最终会抛给 main 方法,这样一来 main 方法中调用了这个方法的时候,就需要解决这个可能出现的异常.
    当然 main 方法也可以不解决异常,将异常往上抛给 JVM ,如果 JVM 也无法解决的话,那么 JVM 就 over 了
  • try-catch-fianlly 真正将异常给处理掉了。throws 只是将异常抛给了方法的调用者,并没有真正将异常处理掉。
  • 之前所述,子类重写父类方法时,不可以抛出新的强制性异常,或者比父类方法声明的更广泛的强制性异常。

7. 如何选择 try-catch-finally 和 throws

什么时候捕捉?什么时候声明?
如果异常发生后需要调用者来处理的,需要调用者知道的,则采用声明方式。否则采用捕捉。

  • 同一方法内, try-catch 和 throws 不要同时使用。因为只要 try-catch 就已经将异常处理掉了,再 throws 没有任何意义
  • 放眼整个程序,try-catch 和 throws 应当联合使用。

例如,在方法标签通过 throws 抛出异常,让调用者决定如何处理。而调用者使用 try-catch 捕获并处理这些异常。

  • 如果父类的方法没有声明抛出任何检查型异常(Checked Exception),则子类在重写这个方法时也不能声明抛出检查型异常。因此,如果子类重写的方法中需要处理异常,必须在方法内部使用 try-catch-finally 进行处理。

如果父类的方法没有使用 throws 声明任何检查型异常,意味着该方法不抛出任何需要强制处理的检查型异常。

  • 在一个复杂的调用链中,建议在较底层的方法中使用 throws 抛出异常,而在较高层的方法中使用 try-catch-finally 来处理这些异常。

假设我们有以下方法调用链:
方法 a 调用 方法 b
方法 b 调用 方法 c

在较底层的方法 b、c ,使用 throws 声明这些方法可能抛出的异常,而不在这些方法内部进行异常捕获和处理。这种做法有以下优点:

异常传播:将异常传递给调用者,让调用者决定如何处理。
简化代码:底层方法专注于核心逻辑,不必处理异常,从而简化代码。

public class Example {

    public static void methodC() throws Exception {
         if (someCondition) {
            throw new Exception("Method C exception");
        }
        // 方法 C 的其他逻辑
    }

    public static void methodB() throws Exception {
        methodC();
        // 方法 B 的其他逻辑
    }

    public static void methodA() {
        try {
            methodB();
        } catch (Exception e) {
            System.err.println("发生错误: " + e.getMessage());
        } finally {
            System.out.println("清理操作");
        }
    }

    public static void main(String[] args) {
        methodA();
    }
}

高层方法使用 try-catch-finally 处理异常

在较高层的方法 a ,使用 try-catch-finally 捕获和处理所有底层方法抛出的异常。这种做法有以下优点:

集中处理:所有异常都在一个地方处理,便于管理和维护。
保证执行:即使发生异常,也可以确保 finally 块中的清理操作能够执行。

为什么要这样做
数据返回问题:如果在方法 b 中使用 try-catch 捕获并处理异常,方法 a 可能无法获得所需的数据。这是因为异常处理可能会中断正常的数据流。
异常聚合:将异常传递到高层方法,允许高层方法集中处理所有可能的异常,提高了代码的可读性和可维护性。

8. 自定义异常类

Java 中允许自定义异常(类)

  • 所有异常都必须是 Throwable 的子类。
  • 如果目标是写一个强制性异常类,则需要继承 Exception 类。
  • 如果目标是写一个运行时异常类,则需要继承 RuntimeException 类。
  • 一个异常类和其它任何类一样,包含有变量和方法。

8.1 自定义异常处理-- 支付余额不足

// 自定义异常类:余额不足
// 继承 Exception 类,该自定义异常是强制性异常,必须在编译之前,由 try-catch 捕获
public class InsufficientFundsException extends Exception
{
  //balanceShortage 储存当出现异常(取出钱多于余额时)所缺乏的钱
  private double balanceShortage;
  public InsufficientFundsException(double balanceShortage)
  {
    this.balanceShortage= balanceShortage;
  } 
  public double getBalanceShortage()
  {
    return balanceShortage;
  }
}
//此类模拟银行账户
public class Account {
	// 区分异常类,此处的 balance 就是单纯的余额
    private double balance;
    private int cardNum;

    public Account (int cardNum) {  this.cardNum = cardNum;  }

    //方法:存钱
    public void deposit(double amount) {
        balance += amount;
    }

    //方法:取钱
    public void withdraw(double amount) throws
            InsufficientFundsException {
        if (amount <= balance) {
            balance -= amount;
        } else {
            double needs = amount - balance;
            throw new InsufficientFundsException(needs);
        }
    }

    //方法:返回余额
    public double getBalance() {  return balance;  }

    //方法:返回卡号
    public int getCardNum() {  return cardNum;  }
}
public class BankDemo
{
   public static void main(String [] args)
   {
      Account account = new Account(101);
      System.out.println("Depositing $500...");
      account.deposit(500.00);
      
      try
      {
         System.out.println("\nWithdrawing $100...");
         account.withdraw(100.00);
         System.out.println("\nWithdrawing $600...");
         account.withdraw(600.00);
      }catch(InsufficientFundsException e)
      {
         System.out.println("Sorry, but you are short $"
                                  + e.getBalanceShortage());
         e.printStackTrace();
      }
    }
}

输出为 :

Depositing $500...

Withdrawing $100...

Withdrawing $600...
Sorry, but you are short $200.0
InsufficientFundsException
        at CheckingAccount.withdraw(CheckingAccount.java:25)
        at BankDemo.main(BankDemo.java:13)

Generics 泛型

  • 泛型是编译阶段的一种强类型约束机制,作用是:把类型的明确工作推迟到创建对象/调用方法时;在编译期间检查类型安全性
  • 泛型的本质是参数化类型 , 也就是将所操作的数据类型指定为一个参数.
  • 泛型的标志,即<>
  • 泛型可以使用在类、方法、接口中

1、以 ArrayList 为例子

我们知道,ArrayList 底层就是一个 Object[] 数组 + 一个变量存储一个当前分配的长度,就可以充当“可变数组”。

public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}
  1. 既然底层是 Object 类型的数组,那就意味着,除了存储 Object 本身外,其他类型都需要强制转型
ArrayList list = new ArrayList();
list.add("apple");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);
  1. 但是,强制转型不仅意味着代码更加麻烦,而且还存在错误转型的可能(导致抛出 ClassCastException ),
list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);
  1. 最朴素的解决思路是,为 String 单独编写一种 ArrayList:
public class StringArrayList {
...
}

这显然不现实,仅 JDK 的 class 就有上千个,难道要为上千个 class 全部配套一个 ArrayList 吗?

  1. 为了解决这个问题,我们把 ArrayList 变成一种模板:ArrayList<T>:
public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

T可以是任何 class。
这样一来,我们就实现了:编写一次模版,可以创建任意类型的 ArrayList:

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

此时,可以总结了:泛型就是定义一种模板,例如 ArrayList。

由编译器针对类型作检查:

ArrayList<String> strList = new ArrayList<String>();
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

泛型类

以 ArrayList 为例子

使用 ArrayList 时,如果不定义泛型类型时,泛型类型实际上就是 Object
此时,只能把 当作 Object 使用,没有发挥泛型的优势。

List list = new ArrayList();
list.add("Hello");
list.add("World");
// list 中的元素是 Object,必须进行强转
String first = (String) list.get(0);
String second = (String) list.get(1);

当我们定义泛型类型 后,List 的泛型接口变为强类型 List:

List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
// 无强制转型:
String first = list.get(0);
String second = list.get(1);

编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。

例如,对于下面的代码,编译器看到泛型类型 List 就可以自动推断出后面的 ArrayList 的泛型类型必须是 ArrayList,因此,可以把代码简写为:

List<String> list = new ArrayList<>();

.1 实例

需求 : 定义一个学生类。类中有一个学号属性,外界既可能对该属性注入 int,还可能注入 String (这里不深究硬伤), 但无论哪种,要求学号属性都能正确接收。

public class Student {
/**    
	private int stuNum;

    public int getStuNum() {  return stuNum; }

    public void setStuNum(int stuNum) {  this.stuNum = stuNum;}
 **/

    private String stuNum;

    public String getStuNum() {  return stuNum; }

    public void setStuNum(String stuNum) {  this.stuNum = stuNum;  }
}

以上的错误做法是无法满足需求的。因为 Java 不允许一个变量同时被声明为两种类型。

  • 若声明为 int , 则在别处调用 set 方法赋值时只能赋 int
  • 若声明为 String , 则在别处调用 set 方法赋值时只能赋 String
.1.1 解决方法1:把 stuNum 声明为 Object 。

既然 Java 不允许一个变量同时被声明为两种类型,那么把该属性声明为根类 Object,自然能存储任何类型。

    public class Student{
    
    private Object stuNum;

    public Object getStuNum() {  return stuNum;  }
    public void setStuNum(Object stuNum) {  this.stuNum = stuNum;  }
}

这种方法有巨大的缺陷。
既然 Object 可以包容所有的类型 , 那么每个 Student 的 stuNum 所存储的东西就无法确定了 , 例如日期/String/int,甚至往 stuNum 中存一个 Student 实例都可以。

.1.2 解决方法2:泛型

将学生类定义为泛型

//将 T 看成一个占位符 , 即先不声明 Student 类的类型,
public class Student<T> {
    //成员属性也是泛型 , 且与类同泛型
    private T t;

    public T getT() {  return t;  }
    public void setT(T t) {  this.t = t;  }
}
public class TestGeneric {
    public static void main(String[] args) {
        //将 String 替代占位符 T,此时属性只能是 String
        Student<String> stringStudent = new Student<>();
        stringStudent.setT("zhinengshiString");
   
        //若实例化对象时也不声明类型,那么默认为Object类型
        Student objectStudent = new Student<>();
    }
}

泛型方法

.1 实例

需求: 在学生类中定义 show 方法 , 并且 show 方法可以接收多种类型的参数

public class Student {

    public String show(String str){  return str;  }

    public Integer show(Integer integer){  return integer;  }
}
public class TestGeneric {
    public static void main(String[] args) {
        Student student = new Student();
        
        student.show("diaoyongString");
        stude6nt.show(18);
    }
}

这里问题与为 ArrayList 例子中的第三点一致:仅 JDK的 class 就有上千个,难道要为上千个 class 全部配套一个方法吗?

解决方法 :泛型方法

修饰符<类型>返回值类型方法名(类型 变量名){..}

public<T> void show(T t){...}
//在普通类中定义泛型方法
/**
 * <T> 传入值采用泛型的方式
 * T: 返回值采用泛型的方式
 * show(T t) :参数列表
 */
public class Student {
    public <T> T show(T t){
        return t;
    }
}

测试类中传值:

public class TestMain {
    public static void main(String[] args) {
        Student student = new Student();
        //根据实际传入的参数类型确定泛型方法show的参数类型
        student.show("testString");
        student.show(20);
    }
}

泛型接口

泛型接口的定义中包含类型参数,从而使得实现该接口的类或使用该接口的代码能够与多种数据类型一起工作,提高了代码的灵活性和重用性。

泛型接口的基本语法是在接口名后面添加尖括号 来声明一个或多个类型参数。实际使用时,可以通过具体的数据类型替换这些类型参数。

public interface GenericInterface<T> {
    void setValue(T value);
    T getValue();
}

在这个例子中,GenericInterface 是一个泛型接口,它有一个类型参数T。接口中两个方法都与类型 T 相关联。

实现泛型接口时,需要指定类型参数的具体类型:

public class MyClass implements GenericInterface<String> {
// 指定了类型参数 T 为 String 类型。
// 因此,setValue 和 getValue 方法现在专门用于处理 String 类型的值。
    private String value;
    
    @Override
    public void setValue(String value) {   this.value = value;  }
    @Override
    public String getValue() {   return value;  }
}

创建泛型接口的实例并使用它:

public class Main {
    public static void main(String[] args) {
        MyClass myObj = new MyClass();
        myObj.setValue("Hello, World!");
        System.out.println(myObj.getValue());  // 输出: Hello, World!
    }
}

泛型的擦除和补偿

泛型的出现提高了编译时的安全性,正因为编译时对添加的数据做了检查,则程序运行时才不会抛出类型转换异常。因此泛型本质上是编译时期的技术,是专门给编译器用的。

  • 泛型的擦除和补偿对程序员是透明的。

泛型擦除:加载类的时候,会将泛型擦除掉(擦除之后的类型为Object类型)。

为什么要有泛型擦除呢?
其本质是为了让 JDK1.4 和 JDK1.5 能够兼容同一个类加载器。
在 JDK1.5 版本中,程序编译时期会对集合添加的元素进行安全检查,如果检查完是安全的、没有错误的,那么就意味着添加的元素都属于同一种数据类型,则加载类时就可以把这个泛型擦除掉,将泛型擦除后的类型就是 Object 类,这样擦除之后的代码就与 JDK1.4 的代码一致。

泛型补偿:在程序运行时,通过获取元素的实际类型进行强转,这就叫做泛型补偿(不必手动实现强制转换)。获得集合中的元素时,JVM 会根据获得元素的实际类型进行向下转型,也就是会恢复获得元素的实际类型,因此我们就无需手动执行向下转型操作,从本质上避免了抛出类型转换异常。

Type Erasure 泛型擦除

泛型是一种类似“模板代码”的技术,不同语言的泛型实现方式不一定相同。

Java 的泛型实现方式是泛型擦除(Type Erasure)。

所谓泛型擦除是指,JVM 对泛型一无所知,所有的工作都是编译器做的。

例如,我们编写了一个泛型类 Pair<T>,这是编译器看到的代码:

public class Pair<T> {

    private T first;
    private T last;
    
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    
    public T getFirst() {  return first;   }
    public T getLast() {   return last;    }
}

而 JVM 根本不知道泛型的存在。这是 JVM 执行的代码:

public class Pair {

    private Object first;
    private Object last;
    
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    
    public Object getFirst() {    return first;    }
    public Object getLast() {    return last;    }
}

因此,Java 使用擦除法实现泛型,导致了:

  • 编译器把类型<T>视为 Object;
  • 编译器根据 <T> 实现安全的强制转型。
  • 使用泛型的时候,我们编写的代码也是编译器看到的代码:
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();

而虚拟机执行的代码并没有泛型:

Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();

所以,Java 的泛型是由编译器在编译时实行的,编译器内部永远把所有类型 T 视为Object 处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

泛型的局限

采用擦除法实现泛型,引入了一些局限性

  • <T>不能是基本类型。例如 int,因为实际类型是 Object,Object 类型无法持有基本类型:
Pair<int> p = new Pair<>(1, 2); // compile error!

Pair<Integer> p = new Pair<>(1,2);// Correct!
  • 不能实例化T类型。例如:new T()。要实例化T类型,我们必须借助额外的Class参数
  • 不能获取带泛型类型的 Class,例如:Pair.class;
  • 不能判断带泛型类型的类型,例如:x instanceof Pair;

泛型通配符

泛型的作用是限定数据类型。

当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型的数据类型,那么在使用的时候只能给其传递和数据类型匹配的类型,否则就会报错。

有的情况下,我们在定义方法时,根本无法确定集合中存储元素的类型是什么。为了解决这个“无法确定集合中存储元素类型”问题,那么 Java 就提供了泛型的通配符。

通配符的几种形式:

  • 无限定通配符,<?>,此处可以为任意引用数据类型。
  • 上限通配符,<? extends Number>,此处“?”必须为 Number 及其子类。
  • 下限通配符,<? super Number>,此处“?”必须为 Number 及其父类。

编写泛型类

编写泛型类比普通类要复杂。
通常来说,泛型类一般用在集合类中,例如 ArrayList,我们很少需要编写泛型类。

如果确实需要编写,步骤如下:

  1. 首先,按照某种类型,例如:String,来编写类:
public class Pair {

    private String first;
    private String last;
    
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {   return first;    }
    public String getLast() {    return last;    }
}
  1. 把特定类型 String 替换为 T,并声明为 :
public class Pair<T> {

    private T first;
    private T last;
    
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {    return first;    }
    public T getLast() {   return last;    }
}

当然,也可以直接从 T 开始编写。

泛型不能用于静态方法

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值