泛型..
1.泛型
所谓泛型 在类定义处是一种类型参数(我们平常所见到的参数指的就是方法中的参数 他接收有外界传递来的值 然后在方法中进行使用) 在类内部的话 则充当一种占位符 并且还提高了代码的复用率
何以见得提高了代码的复用率 其实就是通过对比使用了泛型技术和没有使用泛型技术之间的区别:
以下是没有使用泛型技术的案例
public class Student1 {// 第一个学生类他的分数接收的是一个浮点类型的数据double score;// 定义getter、setter方法void setScore(double score){this.score = score;}double getScore(){return score;}
}
public class Student2 {// 这个学生类接收的是字符串类型的分数String score;// 定义getter、setter方法void setScore(String score){this.score = score;}String getScore(){return score;}
}
public class Student3 {// 第三个学生类的分数就定义为整型int score;// 定义相关的getter、setter方法void setScore(int score){this.score = score;}int getScore(){return score;}
}
从以上代码中我们可以看出 定义了三种不同类型的分数 就要定义三份逻辑相似的代码 如果分数的类型规模一旦庞大起来 那么其实效率是十分低下的
现在引入了泛型技术以后 就不会出现以上这种局面 并且会提高同一份代码的复用率
public class Student<T>{// 泛型是一种类型参数 有外界的实参所决定 并且可以在类中使用T score;// 定义一个getter、setter方法void setScore(T score){this.score = score;}T getScore(){return score;}
}
我们可以在主函数中测试一下这个代码的正确性
public class Main {public static void main(String[] args) {// 我们来试用一下泛型好不好用吧Student<String> s1 = new Student<>();Student<Integer> s2 = new Student<>();Student<Double> s3 = new Student<>();s1.setScore("优秀");s2.setScore(100);s3.setScore(99.5);System.out.println(s1.getScore());System.out.println(s2.getScore());System.out.println(s3.getScore());}}
从结果可以看出 这个泛型类型是写的很成功的 并且从中我们还能够得知 从java7开始 右边<>内就可以不用写出具体的泛型了
而且我们的泛型名称是有命名建议的:
T Type
E Element
K Key
N Number
V Value
S、U、V 当你想要表示连续类型中的第二个、第三个、第四个泛型的时候的命名建议
类名后面不仅可以支持一个泛型 也可以支持多个泛型
public class Student<N, S>{private N no;private S score;public Student(N no, S score){this.no = no;this.score = score;}
}
public class Main {public static void main(String[] args) {Student<Integer, Double> s = new Student<>(1, 100.0);}
}
何为泛型类型?其实就指的是哪些使用了泛型的类或者接口
比如一些诸如java.util.Comparator、java.util.Comparable之类的接口
2.泛型类型的继承
我们从前面的多态都知道 如果类或者接口之间存在父子关系的话 那么让父类引用指向子类对象是可以编译成功的(子类引用也可以指向父类对象 前提是要进行强制转换)
以下的案例中 从结果可以明显发现strBox和intBox不存在父子关系 objBox和strBox之间不存在父子关系
有些人可能会以泛型的继承关系来作为泛型类型的继承关系 那好 我们可以超这个方向继续往下想 如果结论果真是如此的话 那么第一对关系肯定不符合 因为String和Integer压根不存在父子关系 只有第二对中的String和Object之间存在父子关系 好 接着往下想 如果是父子关系的话 多态是可以编译通过 也就是说 strBox和objBox指向的是同一个对象 然后我通过objBox设置了所指对象的element值 他的类型为Object类型 然后通过strBox获取所指对象的element值 并且通过一个String变量进行获取 但是我们可以清楚的发现由于之前element已经被设置为Object类型 所以导致Object赋值给了String 其实如果没有强制转换的话 这个语句是不能通过编译的 但是实际上这个语句是编译通过的 所以说我们刚才的strBox和objBox之间是不存在父子关系的
public class Main {public static void main(String[] args) {Box<String> strBox = new Box<>();Box<Integer> intBox = new Box<>();Box<Object> objBox = new Box<>();strBox = intBox;// errorobjBox = strBox;// error// 如果上面代码是正确的话 那么思考一下下面的代码objBox.setElement(new Object());String str = strBox.getElement();}
}
所以说下面这张图其实清楚的阐明了Number和Integer以及Box之间的关系
接着一个案例
我们都知道jdk中存在这这样如图所示的这样一种关系
由于上述各种接口或者类之间存在这父子关系 所以以下的代码其实都是可以编译通过的
public class Main {public static void main(String[] args) {Iterable<String> it = null;Collection<String> col = null;List<String> li = null;ArrayList<String> al = null;it = col;col = li;li = al;}
}
但是如果是以下代码的话 就不能够编译通过了
假设我们的List和ArrayList都是自定义的(就是非jdk源码 是允许的) 并且他们类或者接口的结构是一样的 那么如果按照刚才那个反例来看的话 那么list和al是父子关系 那么list和al所指对象是一致的(假设list的泛型为Object al的泛型是String) 并且我通过list设置所指对象的no 这个no就是Object类型的 然后通过al获取所指对象的no 并且通过String变量保留 而Object是不能够在没有强制转换的前提下完成这个赋值的 但是实际上这个赋值操作是允许的 所以说list和al其实不是父子关系
public class Main {public static void main(String[] args) {List<Object> list = null;ArrayList<String> al = null;list = al;// error}
}
我可以总结一下就是说
当泛型不一致的话 那么泛型类型就一定不存在父子关系
当泛型一致的话 那么泛型类型也不一定存在父子关系 只有当泛型类型是有继承或者实现关系的话 那么泛型类型才有父子关系
前面说到泛型可以支持连续多个的 当我们两个类或者接口中的第一个泛型是一致的 并且子类可以存在第二个泛型的时候 那么这时候子类和父类之间是存在父子关系的
public interface MyList<E, T> extends List<E> {void setNo(T no);
}
public class Main {public static void main(String[] args) {List<String> li = null;MyList<String, Integer> ml1 = null;MyList<String, Double> ml2 = null;MyList<String, String> ml3 = null;li = ml1;li = ml2;li = ml3;}
}
从结果显示 编译是通过的 说明其实上述的这种操作是完全被允许的
3.原始类型
何为原始类型 其实就是没有为泛型提供一个具体的类型
从上图中 我们想说的是:
当我们使用了原始类型的时候 那么编译器就会警告我们这是一个原始类型 发出rawtypes警告
当我们将非原始类型赋值给原始类型的时候 编译器是很正常的
但是当我们将原始类型赋值给非原始类型的时候 编译器会发出unchecked警告
我们对于以上这些由编译器发出的警告可以通过@SuppressWarnings这个注解进行消除
public class Box<T>{private T no;public T getNo(){return no;}public void setNo(T no){this.no = no;}
}
public class Main {public static void main(String[] args) {Box box = new Box();box.setNo(new Object());Object o = box.getNo();}
}
通过上面这段代码 我想说的是如果是原始类型的话 那么当我们进行getter和setter方法的时候 所涉及到的no都是Object类型的 那么我们可能会有一个疑惑 就是这不是和泛型为Object的泛型类型一样吗 那么这两个等价吗 其实他们两者是有着本质的区别的:
一个是原始类型 一个是非原始类型 只是原始类型的getter和setter方法中涉及到的no都是Object类型的
4.泛型方法
使用了泛型的方法 但是这边有一个误区是 在泛型类型中 如果方法的返回值或者参数为泛型的话 那么不是泛型方法 真正我们去判断一个类或者一个方法是否为泛型类型或者泛型方法的依据是:他们的声明位置处是否有< T>标志 泛型方法的标志一般都是写在返回值之前
现在有一个需求是:
我们想要将setter方法为学生对象赋值的操作封装在一个方法中
public class Student<N, S>{private N no;private S score;public N getNo(){return no;}public void setNo(N no){this.no = no;}public S getScore(){return score;}public void setScore(S score){this.score = score;}
}
public class Main {public static void main(String[] args) {Student<Integer, Integer> stu1 = new Student<>();int no1 = 1;int score1 = 90;set(stu1, no1, score1);Student<String, String> stu2 = new Student<>();String no2 = "2";String score2 = "100";set(stu2, no2, score2);// error}public static void set(Student<Integer, Integer> stu, Integer no, Integer score){stu.setNo(no);stu.setScore(score);}public static void set(Student<String, String> stu, String no, String score){stu.setNo(no);stu.setScore(score);}
}
set(stu2, no2, score2)立马编译失败 这是因为我们set方法中的泛型位置处写死了 不能够接收任意的类型 所以我们应该将set方法设置为泛型方法
public class Main {public static void main(String[] args) {Student<Integer, Integer> stu1 = new Student<>();int no1 = 1;int score1 = 90;set(stu1, no1, score1);Student<String, String> stu2 = new Student<>();String no2 = "2";String score2 = "100";set(stu2, no2, score2);System.out.println(stu1.getNo());System.out.println(stu2.getNo());}public static <N, S> void set(Student<N, S> stu, N no, S score){stu.setNo(no);stu.setScore(score);}
}
从结果来看 这种做法的确完成了我们的需求
但是其实我们想使用泛型类型一样 我们在使用泛型方法的时候 也可以写明这个方法所需的泛型
public class Main {public static void main(String[] args) {Student<Integer, Integer> stu1 = new Student<>();int no1 = 1;int score1 = 90;Main.<Integer, Integer>set(stu1, no1, score1);Student<String, String> stu2 = new Student<>();String no2 = "2";String score2 = "100";<String, String>set(stu2, no2, score2);// errorSystem.out.println(stu1.getNo());System.out.println(stu2.getNo());}public static <N, S> void set(Student<N, S> stu, N no, S score){stu.setNo(no);stu.setScore(score);}
}
但是<String, String>set(……);这种写法肯定是编译失败的
就算你不写完整版本的泛型方法 那么编译器也会自动推断出泛型方法调用时的泛型类型
以下是一个案例:
这个案例中 我们在声明addBox方法的时候 没有写死泛型 所以我们可以不用写出针对不同类型组合的多份类似代码 而是通过引入泛型技术做到代码复用率的提高
1.类型推断
前面我们在调用类型方法的时候 有简化版本和完整版本 我们知道 简化版本编译器会根据你传递的参数类型来获悉你的泛型具体是什么 但是如果一个类型方法不需要传递参数的话 那么编译器应该依据什么推断出泛型方法的泛型呢 其实除了根据参数得知泛型 也可以根据所调用的方法的返回值来获悉
2.构造方法
除了普通方法可以使用泛型技术以外 构造方法其实也可以使用这项技术
可以看到 其实我也没有写明传递给泛型的具体类型是什么 编译器会自动根据我传递的参数类型进行推断 得到具体的泛型
3.细节
在泛型类型中 静态方法是不能够使用来自泛型类型的泛型的 原因在于:
静态方法可以不通过实例调用 但是泛型是需要通过创建实例来进行具体赋值的 泛型没有具体的值 那么你就无法在类中去使用这个泛型 所以泛型其实和实例是相关联的 而静态方法可以不和实例关联
但是你非要在静态方法中使用泛型的话 那么你可以将这个方法升级为泛型方法 那么无论你在调用的时候 写的是完整版本还是简化版本 编译器都可以知道你泛型的具体类型 你就可以安心在静态方法中去使用这个泛型了
5.限制类型参数
我们可以利用extends对类型参数进行设限 比如< T extends A>
extends可以后接接口名或者类名 代表T必须是A类型或者继承、实现A
以上的案例中 我们可以传递Integer给类型参数 但是不能够传递String 而且当我们传递Integer后 可以调用父类的实例方法intValue
而且不仅仅可以添加一个限制 还可以添加多个限制
比如<T extends A & B & C>
但是多个限制存在一些细节:
1.和普通的继承一样 他仅支持单继承 也就是只能够继承一个类
2.和普通的接口一样 他支持多实现 也就是他能够实现多个接口
3.和普通的继承必须写在实现之前一样 他也遵循这个规定
4.不能够写成<T extends A | B | C>这种形式的 因为如果同时满足了A和B的话 可能会产生歧义(因为A | B | C限制了T只能满足其中一种 那么如果同时满足了两种的话 那么到底是满足哪一种呢 这是存在歧义的)
现在有这么一个需求:
就是想要获取一个任意类型的数组中的最大值 但是如果想要获取最大值的话 那么这个类型必须具备可比较性 也就是有这自定义的比较逻辑 所以说这个类型必须实现Comparable接口
public class Student implements Comparable<Student>{private int age;public Student(int age){this.age = age;}@Overridepublic String toString() {return "[" + age + "]";}@Overridepublic int compareTo(Student o) {// 但是如果参数为空的话 那么一定说明调用者比较大if(o == null)return 1;return age - o.age;// 如果返回值如此的话 那么说明年龄大的Student较大}
}
public class Main {public static void main(String[] args) {Integer[] is = {11, 22, 33};System.out.println(getMax(is));// 33Double[] ds = {11.1, 22.2, 33.3};System.out.println(getMax(ds));// 33.3Student[] ss = {new Student(11), new Student(22), new Student(33)};System.out.println(getMax(ss));// 第三个Student类}// 要获取各种类型的数组中的最大值的话 那么需要定义泛型方法才行static <T extends Comparable<T>> T getMax(T[] array){// 如果数组为空或者数组长度为0的话 那么就不执行任何操作if(array == null || array.length == 0)return null;// 设置最大值的初始值为数组首元素T max = array[0];// 然后和剩余的元素挨个比较 如果元素有较大值的话 那么就更新max值for(int i = 1; i < array.length; ++i){// 如果当前的元素为空的话 那么就继续比较下一个元素if(array[i] == null)continue;// 如果当前元素大于max值的话 那么就更新max值if(array[i].compareTo(max) > 0)max = array[i];}return max;}
}
而且需要注意的一点是:你不能够将max值作为调用者 因为如果首元素是空的话 那么最后最大值也只能够是空 即使你有更大的值的话
但是如果你的元素没有提供比较逻辑的话 那么Arrays.sort是无法对你的数组进行比较的
public class Student{private int age;public Student(int age){this.age = age;}@Overridepublic String toString() {return "[" + age + "]";}
}
public class Main {public static void main(String[] args) {Student[] ss = {new Student(11), new Student(22), new Student(33)};Arrays.sort(ss);System.out.println(Arrays.toString(ss));}
}
结果会抛出ClassCastException异常 因为你的元素没有具备可比较性 换句话说就是你的元素类型没有实现Comparable接口 继而没有实现其中的compareTo方法
但是由于我的Arrays.sort方法可以接收另外一个参数 换做比较器 通过比较器其实我们也可以在元素自身没有具备可比较性的基础上是元素具备可比较性 就是设置Comparator接口中的compare方法
public class Student{private int age;public Student(int age){this.age = age;}public void setAge(int age){this.age = age;}public int getAge(){return age;}@Overridepublic String toString() {return "[" + age + "]";}
}
public class Main {public static void main(String[] args) {Student[] ss = {new Student(11), new Student(22), new Student(33)};Arrays.sort(ss, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o1.getAge() - o2.getAge();}});System.out.println(Arrays.toString(ss));}
}
从打印结果来看 其实他也可以完成对我们的Student数组的排序操作 而且排序结果为升序排序
并且我们如果同时定义了两套排序规则 分别来源于自身具备的比较逻辑和比较器中的比较逻辑 那么后者会覆盖前者 称为我们比较过程中的比较逻辑
public class Student implements Comparable<Student>{private int age;public Student(int age){this.age = age;}public void setAge(int age){this.age = age;}public int getAge(){return age;}@Overridepublic String toString() {return "[" + age + "]";}@Overridepublic int compareTo(Student o) {if(o == null)return 1;return age - o.age;}
}
public class Main {public static void main(String[] args) {Student[] ss = {new Student(11), new Student(22), new Student(33)};Arrays.sort(ss, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {return o2.getAge() - o1.getAge();}});System.out.println(Arrays.toString(ss));}
}
从打印结果来看 可知比较器的比较逻辑确实覆盖了自身具备的比较逻辑
并且如果采用的是比较器的方式的话 那么可以继续简化 原因在于Comparator接口是一个函数式接口 所以我们可以将匿名类简化成Lambda表达式
public class Student implements Comparable<Student>{private int age;public Student(int age){this.age = age;}public void setAge(int age){this.age = age;}public int getAge(){return age;}@Overridepublic String toString() {return "[" + age + "]";}@Overridepublic int compareTo(Student o) {if(o == null)return 1;return age - o.age;}
}
public class Main {public static void main(String[] args) {Student[] ss = {new Student(11), new Student(22), new Student(33)};Arrays.sort(ss, (o1, o2) -> o2.getAge() - o1.getAge());System.out.println(Arrays.toString(ss));}
}
但是如果我现在的需求改动了一下 变成了数组元素是Student类 但是他比较逻辑中比较的对象是泛型 那么应该怎么重置Student类
现在的年龄变成了泛型的话 为了使得所有类型都有比较逻辑 具有可比较性 不止是那些内置的Integer、Double有比较逻辑 我们需要重置一下比较逻辑 也就是让泛型实现Comparable 并且要让我们的比较逻辑更为健壮一点 就是增加判空操作
public class Student<T extends Comparable<T>> implements Comparable<Student<T>>{private T age;public Student(T age){this.age = age;}public void setAge(T age){this.age = age;}public T getAge(){return age;}@Overridepublic String toString() {return "[" + age + "]";}@Overridepublic int compareTo(Student<T> o) {if(o == null)return 1;if(age != null)return age.compareTo(o.age);return o.age == null ? 0 : -1;}
}
public class Main {public static void main(String[] args) {Student[] ss = {new Student<>(11), new Student<>(22), new Student<>(33)};System.out.println(Arrays.toString(ss));}
}
6.通配符
在泛型中 ?表示通配符
通配符用作变量类型、返回值类型的类型参数
而且不能用作泛型方法调用、泛型类型实例化、泛型类型定义时的类型参数、泛型方法声明时的类型参数
public class Main {public static void main(String[] args) {Main.<?>test();// error}public static <T> void test(){}
}
public class Person<T> {
}
public class Main {public static void main(String[] args) {Person<?> p = new Person<?>();// 左边允许 右边error}
}
public class Person<?> {// error}
1.通配符–上界
上界 顾名思义就是:保证<? extends xxx>中?是xxx或者xxx的子类型
我们可以通过extends设置参数的上界
现在我有一个这样的需求:
我想要设置一个参数 能够接收不同的泛型类型
public class Main {public static void main(String[] args) {Box<Integer> box1 = new Box<>();Box<String> box2 = new Box<>();Box<Object> box3 = new Box<>();showBox(box1);// errorshowBox(box2);// errorshowBox(box3);// ok}public static void showBox(Box<Object> box){}
}
可以看到 上述解决方案肯定是不可取的 因为不能因为泛型存在父子关系 就简单得出泛型类型存在父子关系 这其实是错的
public class Main {public static void main(String[] args) {Box<Integer> box1 = new Box<>();Box<String> box2 = new Box<>();Box<Object> box3 = new Box<>();showBox(box1);// okshowBox(box2);// okshowBox(box3);// ok}public static <T> void showBox(Box<T> box){}
}
将静态方法升级为泛型方法 这就可以完成上述需求了
public class Main {public static void main(String[] args) {Box<Integer> box1 = new Box<>();Box<String> box2 = new Box<>();Box<Object> box3 = new Box<>();showBox(box1);// okshowBox(box2);// okshowBox(box3);// ok}public static void showBox(Box<?> box){}
}
同样的 采用通配符的方式也能够完成接收不同泛型类型的需求
通配符和一般的泛型是有区别的:
public class Main {public static void main(String[] args) {Box<Integer> box1 = new Box<>();Box<String> box2 = new Box<>();Box<Object> box3 = new Box<>();Box<? extends Number> box4 = new Box<>();Box<Number> box5 = new Box<>();box4 = box1;// okbox5 = box1;// error}
}
从中我们可以知道使用通配符可以定义一些模糊的类型 但是泛型的话 必须是具体化的东西才行 正是通配符的这个特性 使得上述语句成立
现在还有一个需求:
就是利用通配符完成求和操作 并且参与求和的元素必须都是数值才行 可以是浮点数 也可以是整数(只要你的类型是Number的子类型或者同等类型的话 就可以保证这是数值)
public class Main {public static void main(String[] args) {List<Integer> is = Arrays.asList(11, 22, 33);System.out.println(sum(is));// 66.0List<Double> ds = Arrays.asList(11.1, 22.2, 33.3);System.out.println(sum(ds));// 66.6}public static double sum(List<? extends Number> list){double sum = 0.0;for(Number num: list){sum += num.doubleValue();}return sum;}
}
2.通配符–下界
下界 顾名思义就是<? super xxx>中?必须是xxx或者xxx的父类型
我们可以通过super设置参数的下界
public class Main {public static void main(String[] args) {List<Integer> l1 = null;List<Number> l2 = null;List<? super Integer> l3 = null;List<? super Number> l4 = null;testLower(l1);// oktestLower(l2);// oktestLower(l3);// oktestLower(l4);// ok}public static void testLower(List<? super Integer> list){}
}
我们现在有一个需求:
向数组中添加元素
public class Main {public static void main(String[] args) {List<Integer> is = new ArrayList<>();addNumber(is);System.out.println(is);List<Number> ns = new ArrayList<>();addNumber(ns);System.out.println(ns);}public static void addNumber(List<? super Integer> list){for(int i = 0; i < 10; ++i){list.add(i);}}
}
从结果可以知道 符合预期
3.通配符–无限制
?什么类型都可以传递
public class Main {public static void main(String[] args) {Box<Integer> box1 = null;Box<String> box2 = null;Box<Double> box3 = null;Box<Object> box4 = null;Box<? extends Object> box5 = null;Box<? super Object> box6 = null;test(box1);// oktest(box2);// oktest(box3);// oktest(box4);// oktest(box5);// oktest(box6);// ok}public static void test(Box<?> box){}
}
4.通配符–继承
这个图的记忆方式:就是小范围继承自大范围
5.通配符–注意
现在有一个需求:
就是打印任意类型的List中的所有元素
public class Main {public static void main(String[] args) {List<Integer> is = Arrays.asList(11, 22, 33);printList(is);// okList<Double> ds = Arrays.asList(11.1, 22.2, 33.3);printList(ds);// ok}public static void printList(List<?> list){for(Object obj: list){System.out.print(obj + " ");}System.out.println();}
}
结果显示 可以满足需求
编译器有一个特点就是 他在编译的过程中必须要知道类型参数的确切值 否则就会报错
所以他在解析List< E>.set(int index, E element)的时候 由于E位置处为通配符? 无法确定E的真实类型 所以报错了
但是代码提示处这个E他提示为Object类型 这其实是因为他不知道这个具体的类型是什么 但是事实是不管是什么类型 都是Object类型 只是具体就是不知道属于什么类型
public class Main {public static void main(String[] args) {}void foo(List<?> list){Object obj = list.get(0);// oklist.set(0, obj);// errorlist.set(0, list.get(0));// error}
}
但是你实在是想要foo方法中的内容全部都编译通过的话 有一种做法就是定义一个结构类似的泛型方法 然后让foo调用他即可
public class Main {public static void main(String[] args) {}void foo(List<?> list){fooHelper(list);}<T> void fooHelper(List<T> list){T obj = list.get(0);list.set(0, obj);list.set(0, list.get(0));}
}
还有一个细节就是平常的类型参数是不能使用super关键字 只能使用extends 只有通配符才可以同时使用super以及extends两个关键字
7.泛型的使用限制
1.泛型不能够传递基本类型 只能够使用引用类型
public class Main {public static void main(String[] args) {Map<Character, Integer> map = new HashMap<>();// okMap<char, int> map2 = new HashMap<>();// error}
}
2.泛型不能够实例化
public class Student<E> {public void add(){E element = new E();// error}
}
但是如果非要实例化 也不是不行 可以通过Class.newInstance间接做到实例化需求
public class Student<E> {public void add(Class<E> cls) throws Exception {E e = cls.newInstance();// ok}
}
我们从源码中可以看出newInstance的返回值是Class的泛型E 但是具体的原理我是不知情的???
3.不能够定义类型为类型参数的静态变量(也不能用于静态方法上 原因是一致的)
原因在于:静态变量可以不和实例挂钩 但是类型参数必须和实例挂钩
public class Student<E> {private static E age;// error
}
4.不能创建带有泛型的数组
public class Main {public static void main(String[] args) {Student<Integer>[] ss = new Student<>[4];// errorStudent<Integer>[] ss2 = new Student[4];// ok}
}
5.下面的重载是不允许的
public class Main {public static void main(String[] args) {Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok}public static void test(Student<? extends String> box){}public static void test(Student<? extends Integer> box){}
}
public class Main {public static void main(String[] args) {Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok}public static void test(Student<String> box){}public static void test(Student<? extends Integer> box){}
}
public class Main {public static void main(String[] args) {Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok}public static void test(Student<String> box){}public static void test(Student<Integer> box){}
}
6.不能够自定义带反省的异常
public class MyException<T> extends Exception{//error}
7.catch的异常类型不能使用类型参数
public class MyException<T>{void add(){try{System.out.println("add");}catch(T t){// errorSystem.out.println("哈哈");}}<E> void remove(){try{System.out.println("remove");}catch(E e) {// errorSystem.out.println("呵呵");}}
}
8.抛出的异常可以是类型参数
public class MyException<T extends Exception>{public void test() throws T{// ok}
}
相关文章:
泛型..
1.泛型 所谓泛型 在类定义处是一种类型参数(我们平常所见到的参数指的就是方法中的参数 他接收有外界传递来的值 然后在方法中进行使用) 在类内部的话 则充当一种占位符 并且还提高了代码的复用率 何以见得提高了代码的复用率 其实就是通过对比使用了泛型技术和没有使用泛型技…...
Android App开发基础(1)—— App的开发特点
本文介绍基于Android系统的App开发常识,包括以下几个方面:App开发与其他软件开发有什么不一样,App工程是怎样的组织结构又是怎样配置的,App开发的前后端分离设计是如何运作实现的,App的活动页面是如何创建又是如何跳转…...
docker-compose初探
我一直直接使用docker命令来创建容器,没有怎么用过docker-compose。也不知道docker-compose和docker有什么区别,docker-compose有什么好处。 现在我约略认为,docker-compose是一个简化docker命令的工具,或者说,它是能…...
【webrtc】跟webrtc学时间戳、序号类型转换
间隔ms src\modules\congestion_controller\remb_throttler.ccnamespace {constexpr TimeDelta kRembSendInterval = TimeDelta::Millis(200); } // namespace百分比的处理 src\modules\congestion_controller\remb_throttler.ccvoid RembT...
文件操作与IO(一些小项目)
在指定目录中寻找文件 扫描指定目录,并找到名称对应的所有文件(不包含目录). import java.io.File; import java.util.Scanner;public class Project1 {public static void main(String[] args) {//1.输入必要的信息Scanner sc new Scanner(System.in);System.out.println(&…...
C语言-算法-线性dp
[USACO1.5] [IOI1994]数字三角形 Number Triangles 题目描述 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 在上面的样例中,从 7 → 3 → 8 →…...
Pandas应用-股票分析实战
股票时间序列 时间序列: 金融领域最重要的数据类型之一 股价、汇率为常见的时间序列数据 趋势分析: 主要分析时间序列在某一方向上持续运动 在量化交易领域,我们通过统计手段对投资品的收益率进行时间序列建模,以此来预测未来的收…...
Database history tablesupgraded
zabbix升级到6之后,配置安装完成会有一个红色输出,但是不影响zabbix使用,出于强迫症,找到了该问题的解决方法。 Database history tables upgraded: No. Support for the old numeric type is deprecated. Please upgrade to nume…...
Dify学习笔记-应用发布(四)
1、发布为公开 Web 站点 使用 Dify 创建 AI 应用的一个好处在于,你可以在几分钟内就发布一个可供用户使用的 Web 应用,该应用将根据你的 Prompt 编排工作。 如果你使用的是自部署的开源版,该应用将运行在你的服务器上 如果你使用的是云服务&…...
优化用户体验测试应用领域:提升产品质量与用户满意度
在当今数字化时代,用户体验测试应用已经成为确保产品质量、提升用户满意度的关键工具。随着技术的不断发展,用户的期望也在不断演变,因此,为了保持竞争力,企业必须将用户体验置于产品开发的核心位置。本文将探讨用户体…...
顶顶通呼叫中心中间件机器人压力测试配置(mod_cti基于FreeSWITCH)
介绍 顶顶通呼叫中心中间件机器人压力测试(mod_cit基于FreeSWITCH) 一、配置acl.conf 打开ccadmin-》点击配置文件-》点击acl.conf-》我这里是已经配置好了的,这里的192.168.31.145是我自己的内网IP,你们还需要自行修改 二、配置线路 打开ccadmin-&g…...
Debezium发布历史87
原文地址: https://debezium.io/blog/2020/03/19/integration-testing-for-change-data-capture-with-testcontainers/ 欢迎关注留言,我是收集整理小能手,工具翻译,仅供参考,笔芯笔芯. 使用 Testcontainer 进行变更数…...
Leetcode131.分割回文串-Palindrome Patitioning-Python-回溯法
解题思路: 1.切割回文串,可以用解决找组合问题的思路解决,而解决组合问题,可以用回溯法,故本题选择回溯法。 2.理解两个事情:1.递归函数里的for循环是横向遍历给定字符串s的每一个字母。2.针对s的每一个字…...
Java面试——基础篇
目录 1、java语言有哪些优点和缺点? 2、JVM 、 JDK 和 JRE的关系 3、为什么说 Java 语言“编译与解释并存”? 4、Java和c的区别 5、基本数据类型 5.1、java的8种基本数据类型: 5.2、基本类型和包装类型的区别: 5.3、包装类型的缓存机…...
C++——结构体
1,结构体基本概念 结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。像int(整型),浮点型,bool型,字符串型等都是属于系统内置的数据类型。而今天要学习的结构体则是属于我们自定义…...
C++技术要点总结, 面试必备, 收藏起来慢慢看
目录 1. 语言对比 1.1 C 11 新特性 2.2 C 和 C 的区别 2.3 Python 和 C 的区别 2. 编译内存相关 2.1. C 程序编译过程 2.2. C 内存管理 2.3. 栈和堆的区别 2.4. 变量的区别 2.5. 全局变量定义在头文件中有什么问题? 2.6. 内存对齐 2.7. 什么是内存泄露 …...
VR数字展厅,平面静态跨越到3D立体化时代
近些年,VR的概念被越来越多的人提起,较为常见的形式就是VR数字展厅。VR数字展厅的出现,让各地以及各行业的展厅展馆的呈现和宣传都发生了很大的改变和革新,同时也意味着展览传播的方式不再局限于原来的图文、视频,而是…...
Linux中LVM实验
LVM实验: 1、分区 -L是大小的意思-n名称的意思 从vg0(卷组)分出来 2、格式化LV逻辑卷 LVM扩容 如果icdir空间不够了, 扩展空间lvextend -L 5G /dev/vg0/lv1 /dev/vg0/lv1(pp,vg,lv) 刷新文件系统xfs_growfs /lvdir VG扩容 …...
外包干了一个月,技术退步明显。。。。。
先说一下自己的情况,本科生,19年通过校招进入南京某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...
gitlab.rb主要配置
根据是否docker安装,进入挂载目录或安装目录 修改此文件,我一般是在可视化窗口中修改,有时候也在命令行手敲 将下面的配置复制到该文件中 external_url http://192.168.100.50 # nginx[listen_port] = 8000 (docker安装的这一行不需要,因为端口映射导致此处修改会导致访问…...
网络协议基础
tcp/ip协议簇 TCP/IP协议族 网络接口层(没有特定的协议) 物理层 数据链路层 网络层: IP (v4/v6) ARP(地址解析协议) RARP . ICMP (Internet控制报文协议) IGMP 传输层: TCP (传输控制协议) UDP (用户数据报协议) 应用层: 都是基于传输层协议的端口,总共端口0~65535 …...
Mac使用adb调试安卓手机
0x00 背景 最近windows电脑休息,用mac办公比较多,手机用时间长了,不太灵光,准备修理一番。于是要用mac调试下android手机。配置略显麻烦,网上的步骤多参差不齐。估计是入门步骤,大佬们也懒得写的太细。于是…...
Web 开发 1: Flask 框架介绍和使用
在 Web 开发中,Flask 是一个流行且灵活的 Python Web 框架,用于构建 Web 应用程序。它简洁而易于上手,适用于小型到中型的项目。在本篇博客中,我将为你介绍 Flask 框架的基础知识和常用技巧,帮助你更好地掌握 Web 开发…...
Centos7.6之禅道开源版17.6.1安装记录
Centos7.6之禅道开源版17.6.1安装记录 文章目录 Centos7.6之禅道开源版17.6.1安装记录1. 下载2. 安装3. 登录4. 连接数据库1. 本地连接2. 远程连接1. 开启远程访问用户2. 更改mysql绑定的主机3. 重启Apache与MySQL服务 4. 常用命令1. Apache和Mysql常用命令2. 其他 1. 下载 官网…...
有趣的代码(简单)
1.代码1 #include<stdio.h> #include<string.h> #include<windows.h> #define _CRT_SECURE_NO_WARNINGS 1 void love() {system("color 4");printf(" **** ***************** ** …...
Java和Redis实现一个简单的热搜功能
1. 前言 我们有一个简单的需求: 搜索栏展示当前登陆的个人用户的搜索历史记录,删除个人历史记录。用户在搜索栏输入某字符,则将该字符记录下来 以zset格式存储的redis中,记录该字符被搜索的个数以及当前的时间戳 (用…...
超越传统,想修哪里就修哪里,SUPIR如何通过文本提示实现智能图像修复
项目简介 通过参数增加使得模型不仅能够修复图像中的错误或损坏,还能根据文本提示进行智能修复。例如根据描述来改变图像中的特定细节。这样的处理方式提升了图像修复的质量和智能度,使得模型能够更准确、更灵活地恢复和改进图像。 SUPIR的主要功能图像…...
《如何画好架构图》学习笔记
看了一堂《如何画好架构图》的公开课,结合网上的资料与经验做一些思考总结。文中的例子和图片大多是从课程中摘录的。 1. 4R架构定义 4R架构定义其实是软件架构定义经过归纳提炼后的简称。 软件架构定义:软件架构是指软件系统的顶层(Rank&am…...
redis整合
一.redis的发布订阅 什么 是发布和订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 1、Redis的发布和订阅 客户端订阅频道发布的消息 频道发布消息 订阅者就可以…...
开循环低温样品架节约液氦操作技巧
开循环低温样品架以降温快、无轰动源、重量轻、装置便利等特色遭到大多数客户的喜爱。但是制冷剂消耗量引起的运用本钱是客户在运用过程中zhong点重视的问题,特别是随着全球液氦价格继续飙升,开循环样品架的运用本钱也在逐渐添加,如何节约液氦…...
公司做网站怎么构思/网络零售的优势有哪些
这篇文章主要为大家详细介绍了Android使用Recyclerview实现图片水平自动循环滚动效果,实现精彩的跑马灯效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 本篇博客主要介绍的是如何使用RecyclerView实现图片水平方向自动循环࿰…...
wordpress iis 中文/东莞seo技术
小程序 后台...
合肥做网站/西安seo关键字优化
原标题:华为突然宣布,鸿蒙OS正式版6月底全面升级,幸福来得太突然华为突然宣布,鸿蒙OS正式版6月底全面升级,幸福来得太突然摘要:早在今年2月华为Mate X2折叠屏新品发布会上,余承东曾表示…...
司法网站建设与维护 教材/怎么做网页设计的页面
转载于:https://www.cnblogs.com/lovelife20/p/4157923.html...
石家庄专业信息门户网站定制/网站宣传的方法有哪些
人的一生,从推开生命之门那一刻开始,于是,你走进生命的殿堂,像一个好奇的旅客,每一件新奇的事物都想去探究,每一个美好的东西都想拥为己有。殿堂太大了,而旅程始终是有时间限定的,不…...
四川酒店网站建设/google推广妙招
在前面的文章中,我们了解CoreData据是什么。本文我们将深入到Core Data框架的内部,掌握一下Core Data Stack的重要组成部分 managed object modelmanaged object contextpersistent store coordinatorJietu20200407-1932362x.jpg文章推荐 SwiftUI CoreD…...