Java泛型
泛型
代码语言:javascript复制public class Score {
String name;
String id;
Object value; //因为Object是所有类型的父类,因此既可以存放Integer也能存放String
public Score(String name, String id, Object value) {
this.name = name;
this.id = id;
this.score = value;
}
}
public static void main(String[] args) {
Score score = new Score("数据结构与算法基础", "EP074512", "优秀"); //是String类型的
...
Integer number = (Integer) score.score; //获取成绩需要进行强制类型转换,虽然并不是一开始的类型,但是编译不会报错
}
使用Object类型作为引用,由于是Object类型,所以说并不能直接判断存储的类型到底是String还是Integer,取值只能进行强制类型转换,显然无法在编译期确定类型是否安全
项目中代码量非常之大,进行类型比较又会导致额外的开销和增加代码量,如果不经比较就很容易出现类型转换异常,代码的健壮性有所欠缺
JDK 5新增了泛型,它能够在编译阶段就检查类型安全,大大提升开发效率
泛型类
泛型其实就一个待定类型,我们可以使用一个特殊的名字表示泛型,泛型在定义时并不明确是什么类型,而是需要到使用时才会确定对应的泛型类型。
代码语言:javascript复制public class Score<T> { //泛型类需要使用<>,我们需要在里面添加1 - N个类型变量
String name;
String id;
T value; //T会根据使用时提供的类型自动变成对应类型
public Score(String name, String id, T value) { //这里T可以是任何类型,但是一旦确定,那么就不能修改了
this.name = name;
this.id = id;
this.value = value;
}
}
代码语言:javascript复制public static void main(String[] args) {
Score<String> score = new Score<String>("计算机网络", "EP074512", "优秀");
//因为现在有了类型变量,在使用时同样需要跟上<>并在其中填写明确要使用的类型
//这样我们就可以根据不同的类型进行选择了
String value = score.value; //一旦类型明确,那么泛型就变成对应的类型了
System.out.println(value);
}
泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译
因为是具体使用对象时才会明确具体类型,所以说静态方法中是不能用的
不能通过这个不确定的类型变量就去直接创建对象和对应的数组
具体类型不同的泛型类变量,不能使用不同的变量进行接收
如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用?
通配符:
public static void main(String[] args) {
Test<?> test = new Test<Integer>();
test = new Test<String>();
Object o = test.value; //但是注意,如果使用通配符,那么由于类型不确定,所以说具体类型同样会变成Object
}
泛型只能确定为一个引用类型,基本类型是不支持的
如果要存放基本数据类型的值,我们只能使用对应的包装类:
代码语言:javascript复制public static void main(String[] args) {
Test<Integer> test = new Test<>();
}
如果是基本类型的数组,因为数组本身是引用类型,所以说是可以的
通过使用泛型,我们就可以将某些不明确的类型在具体使用时再明确。
泛型与多态
不只是类,包括接口、抽象类,都是可以支持泛型的:
代码语言:javascript复制public interface Study<T> {
T test();
}
代码语言:javascript复制public class Main {
public static void main(String[] args) {
A a = new A();
Integer i = a.test();
}
static class A implements Study<Integer> {
//在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型
@Override
public Integer test() {
return null;
}
}
}
代码语言:javascript复制public class Main {
public static void main(String[] args) {
A<String> a = new A<>();
String i = a.test();
}
static class A<T> implements Study<T> {
//让子类继续为一个泛型类,那么可以不用明确
@Override
public T test() {
return null;
}
}
}
继承也是同样的
泛型方法
返回值前的< T >非常重要,可以理解为声明此方法为泛型方法。
只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
< T >表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
代码语言:javascript复制public class Main {
public static void main(String[] args) {
String str = test("Hello World!");
}
private static <T> T test(T t){ //在返回值类型前添加<>并填写泛型变量表示这个是一个泛型方法
return t;
}
}
泛型方法会在使用时自动确定泛型类型,比如上我们定义的是类型T作为参数,同样的类型T作为返回值,实际传入的参数是一个字符串类型的值,那么T就会自动变成String类型,因此返回值也是String类型。
泛型的界限
不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:
代码语言:javascript复制public class Score<T extends Number> { //设定类型参数上界,必须是Number或是Number的子类
private final String name;
private final String id;
private final T value;
public Score(String name, String id, T value) {
this.name = name;
this.id = id;
this.value = value;
}
public T getValue() {
return value;
}
}
当我们在使用变量时,泛型通配符也支持泛型的界限:
代码语言:javascript复制public static void main(String[] args) {
Score<? extends Integer> score = new Score<>("数据结构与算法", "EP074512", 60);
}
下界仅适用于通配符:
代码语言:javascript复制public static void main(String[] args) {
Score<? super Number> score = new Score<>("数据结构与算法基础", "EP074512", 10);
Object o = score.getValue();
}
类型擦除
实际上在Java中并不是真的有泛型类型(为了兼容之前的Java版本)
因为所有的对象都是属于一个普通的类型,一个泛型类型编译之后,实际上会直接使用默认的Object类型
代码语言:javascript复制public abstract class A {
abstract Object test(Object t); //默认就是Object
}
代码语言:javascript复制public abstract class A <T extends Number>{ //设定上界为Number
abstract T test(T t);
}
public abstract class A {
abstract Number test(Number t); //上界Number,因为现在只可能出现Number的子类
}
由于类型擦除,实际上我们在使用时,编译后的代码是进行了强制类型转换的:
代码语言:javascript复制public static void main(String[] args) {
A<String> a = new B();
String i = a.test("10"); //因为类型A只有返回值为原始类型Object的方法
}
代码语言:javascript复制public static void main(String[] args) {
A a = new B();
String i = (String) a.test("10"); //依靠强制类型转换完成的
}
既然继承泛型类之后可以明确具体类型,那么为什么@Override
不会出现错误呢?我们前面说了,重写的条件是需要和父类的返回值类型和形参一致,而泛型默认的原始类型是Object类型,子类明确后变为其他类型,这显然不满足重写的条件,但是为什么依然能编译通过呢?
public class B extends A<String>{
@Override
String test(String s) {
return null;
}
}
通过反编译进行观察,实际上是编译器帮助我们生成了一个桥接方法用于支持重写:
代码语言:javascript复制public class B extends A {
public Object test(Object obj) { //这才是重写的桥接方法
return this.test((String) obj); //桥接方法调用我们自己写的方法
}
public String test(String str) { //我们自己写的方法
return null;
}
}
类型擦除机制其实就是为了方便使用后面集合类(不然每次都要强制类型转换)同时为了向下兼容采取的方案。
泛型的使用会有一些限制:
在进行类型判断时,不允许使用泛型,只能使用原始类型:
代码语言:javascript复制Test<String> test = new Test<>();
System.out.println(test instanceof Test); //在进行类型判断时,不允许使用泛型,只能使用原始类型
泛型类型是不支持创建参数化类型数组的:
代码语言:javascript复制public static void main(String[] args) {
Test<Stirng>[] test = new Test<String>[10];//err
//同样是因为类型擦除导致的,运行时可不会去检查具体类型是什么
}
只不过只是把它当做泛型类型的数组还是可以用的:
代码语言:javascript复制public static void main(String[] args) {
Test<String>[] test = new Test[10];
test[0] = new Test<String>();
}
函数式接口
函数式接口就是JDK1.8专门为我们提供好的用于Lambda表达式的接口,这些接口都可以直接使用Lambda表达式,非常方便,这里我们主要介绍一下四个主要的函数式接口:
Supplier供给型函数式接口:这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象。
代码语言:javascript复制@FunctionalInterface //函数式接口都会打上这样一个注解
public interface Supplier<T> {
T get(); //实现此方法,实现供给功能
}
代码语言:javascript复制//专门供给Student对象的Supplier
private static final Supplier<Student> STUDENT_SUPPLIER = Student::new;
public static void main(String[] args) {
Student student = STUDENT_SUPPLIER.get();
student.hello();
}
Consumer消费型函数式接口:这个接口专门用于消费某个对象的
代码语言:javascript复制@FunctionalInterface
public interface Consumer<T> {
void accept(T t); //这个方法就是用于消费的,没有返回值
default Consumer<T> andThen(Consumer<? super T> after) { //这个方法便于我们连续使用此消费接口
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
代码语言:javascript复制//专门消费Student对象的Consumer
private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student " 真好吃!");
public static void main(String[] args) {
Student student = new Student();
STUDENT_CONSUMER.accept(student);
}
使用andThen
方法继续调用:
public static void main(String[] args) {
Student student = new Student();
STUDENT_CONSUMER //我们可以提前将消费之后的操作以同样的方式预定好
.andThen(stu -> System.out.println("我是吃完之后的操作!"))
.andThen(stu -> System.out.println("好了好了,吃饱了!"))
.accept(student); //预定好之后,再执行
}
Function函数型函数式接口:这个接口消费一个对象,然后会向外供给一个对象(前两个的融合体)
代码语言:javascript复制@FunctionalInterface
public interface Function<T, R> {
R apply(T t); //这里一共有两个类型参数,其中一个是接受的参数类型,还有一个是返回的结果类型
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
首先还是最基本的apply
方法,这个是我们需要实现的:
//这里实现了一个简单的功能,将传入的int参数转换为字符串的形式
private static final Function<Integer, String> INTEGER_STRING_FUNCTION = Object::toString;
public static void main(String[] args) {
String str = INTEGER_STRING_FUNCTION.apply(10);
System.out.println(str);
}
使用compose
将指定函数式的结果作为当前函数式的实参:
public static void main(String[] args) {
String str = INTEGER_STRING_FUNCTION
.compose((String s) -> s.length()) //将此函数式的返回值作为当前实现的实参
.apply("lbwnb"); //传入上面函数式需要的参数
System.out.println(str);
}
andThen
可以将当前实现的返回值进行进一步的处理,得到其他类型的值:
public static void main(String[] args) {
Boolean str = INTEGER_STRING_FUNCTION
.andThen(String::isEmpty) //在执行完后,返回值作为参数执行andThen内的函数式,最后得到的结果就是最终的结果了
.apply(10);
System.out.println(str);
}
Function中还提供了一个将传入参数原样返回的实现:
代码语言:javascript复制public static void main(String[] args) {
Function<String, String> function = Function.identity(); //原样返回
System.out.println(function.apply("不会吧不会吧,不会有人听到现在还是懵逼的吧"));
}
Predicate断言型函数式接口:接收一个参数,然后进行自定义判断并返回一个boolean结果。
代码语言:javascript复制@FunctionalInterface
public interface Predicate<T> {
boolean test(T t); //这个方法就是我们要实现的
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
代码语言:javascript复制private static final Predicate<Student> STUDENT_PREDICATE = student -> student.score >= 60;
public static void main(String[] args) {
Student student = new Student();
student.score = 80;
if(STUDENT_PREDICATE.test(student)) { //test方法的返回值是一个boolean结果
System.out.println("及格了,真不错,今晚奖励自己一次");
} else {
System.out.println("不是,Java都考不及格?隔壁初中生都在打ACM了");
}
}
使用组合条件判断:
代码语言:javascript复制public static void main(String[] args) {
Student student = new Student();
student.score = 80;
boolean b = STUDENT_PREDICATE
.and(stu -> stu.score > 90) //需要同时满足这里的条件,才能返回true
.test(student);
if(!b) System.out.println("Java到现在都没考到90分?你的室友都拿国家奖学金了");
}
判空包装
Java8还新增了一个非常重要的判空包装类Optional,这个类可以很有效的处理空指针问题。
代码语言:javascript复制private static void test(String str){
Optional
.ofNullable(str) //将传入的对象包装进Optional中
.ifPresent(s -> System.out.println("字符串长度为:" s.length()));
//如果不为空,则执行这里的Consumer实现
}
代码语言:javascript复制private static void test(String str){
String s = Optional.ofNullable(str).get(); //get方法可以获取被包装的对象引用,但是如果为空的话,会抛出异常
System.out.println(s);
}
代码语言:javascript复制private static void test(String str){
String s = Optional.ofNullable(str).orElse("我是为null的情况备选方案");
System.out.println(s);
}
代码语言:javascript复制private static void test(String str){
Integer i = Optional
.ofNullable(str)
.map(String::length) //使用map来进行映射,将当前类型转换为其他类型,或者是进行处理
.orElse(-1);
System.out.println(i);
}