例子:
class Score<T>{
T value;
public Score(T value){
this.value = value;
}
}
public class fanxing {
public static void main(String[] args) {
Score<String > score = new Score<>("aaa");
System.out.println(score.value);
}
}
泛型将数据类型的确定控制在了编译阶段
我们在方法中使用待确定类型的变量时
因为泛型本身就是对某些待定类型的简单处理,如果都明确要使用什么类型了,那大可不必使用泛型。
如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用?
通配符
Score<?> score = new Score<>("aaa");
score = new Score<Integer>(111);
Object object = score.value;//但是注意,如果使用通配符,那么由于类型不确定,所以说具体类型同样会变成Object
System.out.println(object);//111
当然,泛型变量不止可以只有一个,如果需要使用多个的话,我们也可以定义多个:
public class Test<A, B, C> { //多个类型变量使用逗号隔开
public A a;
public B b;
public C c;
}
如果要存放基本数据类型的值,我们只能使用对应的包装类:
Test<Integer> test = new Test<>();
当然,如果是基本类型的数组,因为数组本身是引用类型,所以说是可以的:
public static void main(String[] args) {
Test<int[]> test = new Test<>();
}
不只是类,包括接口、抽象类,都是可以支持泛型的:
public interface Study<T> {
T test();
}
当子类实现此接口时
我们可以选择在实现类明确泛型类型
interface Study4<T>{
T test();
}
class A implements Study4<Integer>{//在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型
@Override
public Integer test() {
return null;
}
}
或是继续使用此泛型让具体创建的对象来确定类型:
interface Study4<T>{
T test();
}
class A<T> implements Study4<T>{//让子类继续为一个泛型类,那么可以不用明确
@Override
public T test() {
return null;
}
}
继承也是同样的:
static class A<T> {
}
static class B extends A<String> {
}
当然,类型变量并不是只能在泛型类中才可以使用,我们也可以定义泛型方法。
当某个方法(无论是是静态方法还是成员方法)需要接受的参数类型并不确定时,我们也可以使用泛型来表示:
在**返回值类型前添加<>**并填写泛型变量表示这个是一个泛型方法
private static <T> T test(T t){
return t;
}
实际上泛型方法在很多工具类中也有,比如说Arrays的排序方法:
Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
Arrays.sort(arr, new Comparator<Integer>() {
//通过创建泛型接口的匿名内部类,来自定义排序规则,因为匿名内部类就是接口的实现类,所以说这里就明确了类型
@Override
public int compare(Integer o1, Integer o2) { //这个方法会在执行排序时被调用(别人来调用我们的实现)
return 0;
}
});
Lambda表达式,像这种只有一个方法需要实现的接口,直接安排了:
Integer[] arr = {1,3,4,523,1,2,2};
Arrays.sort(arr, (o1, o2) -> {
return o1 - o2;
});
System.out.println(Arrays.toString(arr));
包括数组复制方法:
public static void main(String[] args) {
String[] arr = {"AAA", "BBB", "CCC"};
String[] newArr = Arrays.copyOf(arr, 3); //这里传入的类型是什么,返回的类型就是什么,也是用到了泛型
System.out.println(Arrays.toString(newArr));
}
现在有一个新的需求
现在没有String类型的成绩了
但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:
class Score<T extends Number>{ //设定类型参数上界,必须是Number或是Number的子类
T value;
public Score(T value){
this.value = value;
}
}
只需要在泛型变量的后面添加extends
关键字即可指定上界
实际上就像这样:
同样的,当我们在使用变量时,泛型通配符也支持泛型的界限:
Score extends Integer> score = new Score<>(666);
下界
只不过下界仅适用于通配符,对于类型变量来说是不支持的。下界限定就像这样:
编译时的类型检查
可以不指定类型,直接绕过检查,使用原始 object
Score score = new Score(111);
score.value = "222";
System.out.println(score.value);//222
最终编译完 --》 有上界用上界类型,没有上界就是 object 类型
实际上在Java中并不是真的有泛型类型(为了兼容之前的Java版本)因为所有的对象都是属于一个普通的类型
一个泛型类型编译之后,实际上会直接使用默认的类型:
public abstract class A {
abstract Object test(Object t); //默认就是Object
}
当然,如果我们给类型变量设定了上界,那么会从默认类型变成上界定义的类型:
public abstract class A <T extends Number>{ //设定上界为Number
abstract T test(T t);
}
那么编译之后:
public abstract class A {
abstract Number test(Number t); //上界Number,因为现在只可能出现Number的子类
}
因此,泛型其实仅仅是在编译阶段进行类型检查
同样的,由于类型擦除,实际上我们在使用时,编译后的代码是进行了强制类型转换的:
public static void main(String[] args) {
A<String> a = new B();
String i = a.test("10"); //因为类型A只有返回值为原始类型Object的方法
}
实际上编译之后:
public static void main(String[] args) {
A a = new B();
String i = (String) a.test("10"); //依靠强制类型转换完成的
}
不支持创建泛型数组
这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象。
@FunctionalInterface //函数式接口都会打上这样一个注解
public interface Supplier {
T get(); //实现此方法,实现供给功能
}
比如我们要实现一个专门供给Student对象Supplier,就可以使用:
public class Student {
public void hello(){
System.out.println("我是学生!");
}
}
//专门供给Student对象的Supplier
private static final Supplier<Student> STUDENT_SUPPLIER = Student::new;
public static void main(String[] args) {
Student student = STUDENT_SUPPLIER.get();
student.hello();
}
这个接口专门用于消费某个对象的。
@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); };
}
}
使用起来也是很简单的:
//专门消费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
方法继续调用:
这个接口消费一个对象,然后会向外供给一个对象(前两个的融合体)
@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中还提供了一个将传入参数原样返回的实现:
public static void main(String[] args) {
Function<String, String> function = Function.identity(); //原样返回
System.out.println(function.apply("不会吧不会吧,不会有人听到现在还是懵逼的吧"));
}
接收一个参数,然后进行自定义判断并返回一个boolean结果。
@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);
}
}
例:
public class Student {
public int score;
}
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了");
}
}
我们也可以使用组合条件判断:
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分?你的室友都拿国家奖学金了");
}
同样的,这个类型提供了一个对应的实现,用于判断两个对象是否相等:
public static void main(String[] args) {
Predicate<String> predicate = Predicate.isEqual("Hello World"); //这里传入的对象会和之后的进行比较
System.out.println(predicate.test("Hello World"));
}
判空包装类Optional,这个类可以很有效的处理空指针问题。
private static void test(String str){
Optional
.ofNullable(str) //将传入的对象包装进Optional中
.ifPresent(s -> System.out.println("字符串长度为:"+s.length())); //如果不为空,则执行这里的Consumer实现
}
我们再获取时可以优雅地处理为空的情况:
我们可以对于这种有可能为空的情况进行处理,如果为空,那么就返回另一个备选方案:
private static void test(String str){
String s = Optional.ofNullable(str).orElse("我是为null的情况备选方案");
System.out.println(s);
}
将包装的类型直接转换为另一种类型:
private static void test(String str){
Integer i = Optional
.ofNullable(str)
.map(String::length) //使用map来进行映射,将当前类型转换为其他类型,或者是进行处理
.orElse(-1);
System.out.println(i);
}