说到泛型,先从List说起,先上一段代码
List list=new ArrayList();//原生态类型
list.add("a");
list.add("b");
list.add(3);
Object o=list.get(0);//object 类型使用需要强转
String os= (String) o;
System.out.println(o);//输出默认转为string,调用的是String.valueOf(o);
List<String> listString=new ArrayList<>();//泛型类型
listString.add("c");
listString.add("d");
// listString.add(3); //这里编译器提示错误,这不肯定的吗,因为在声明的时候已经把这个list定义为只能放入String类型
String s= listString.get(0);//在这取出的对象就是String,不需要强转,保证安全
System.out.println(s);
/*
容器使用泛型的好处,安全性:定义了String,编译器就不让往该容器里放其他类型内容;便利性:取出参数不需要强转;表述性:能看见容器里元素类型
*/
List<?> listUnknow =new ArrayList<>();
// listUnknow.add("e"); //这里编译器提示错误,List<?> 类型 编译器不知道?到底是什么类型,无法保证安全性,所以无法添加与修改,但可以删除
listUnknow=listString;
String s0= (String) listUnknow.get(0);//读取出来的是Object类型,需要强转
System.out.println(s0);
List<Object> listObject =new ArrayList<>();
listObject.add("f");
listObject.add("g");
listObject.add(3);
//看起来和 原生list没区别,但是
list=listUnknow;
// listObject=listUnknow;//这里编译器提示错误,没错,在接受赋值的时候就出现问题了
先来看看List的这几种声明方式,List,List<?>,List<Object>,这三者的区别在哪,这里一个个看.
首先List,在java SE5之前没有泛型这个概念,声明list使用原生态类型,这种类型容许往容器中放入任何内容(Object),在取出数据的时候也是Object类型,需要使用的时候就不得不强转,所以没有安全性;
再来看下一个List<String> ,泛型类型,并且声明容器内只能放入String,当往这个list中添加其他类型对象时,编译器提示错误,从该容器中取出的对象也只会是String,这便保证了安全性,容器使用泛型的好处如同代码所示:安全性:定义了String,编译器就不让往该容器里放其他类型内容;便利性:取出参数不需要强转;表述性:能看见容器里元素类型;
往下再看看List<?> ,定义时,告诉编译器,我也不知道它到底是什么类型,既然如此,那就让他无法添加和修改吧(null除外),所以listUnknow对象其实是只读的,你可以给它赋值,但是不能做修改和添加,当然,删除还是可以的,毕竟删除(remove)不需要关注里面到底是什么类型.从List<?>中取值出来的对象为Object,需要使用的时候要强转.
轮到List<Object>,声明了一个listObject 对象,它能添加能删除,而且还是Object,来者不拒.因为声明了是Object对象,所以取出来的对象也是Object,需要强转.这么看来好像和原生态List没啥区别?就目前看来两者挺像,但是当给list原生态对象和listObject对象赋值时,发生了一点问题,listObject好像不能接收List<?>,List<String>的赋值,而原生态list来者不拒,这里涉及到了泛型的擦除机制,List<String>,List<Integer>在编译器编译之后,最终都会转为List原生类型.
再说泛型接口,泛型接口先上一段代码
首先理一下结构,Child为Parent子类,Parent为GrandParent子类,分别有doSome及showMe方法,然后是泛型接口TService<T>,当然如果对于类型有限制也可写成<T extends Number>,Number可变换为需要的类型,具体情况具体分析.泛型接口定义了两个方法,分别是getT()与dosomeT(T t),具体代码如下:
public interface TService<T> {// 如果有类型限制 也可写成 TService<T extends Number>
T getT();//T 可以是返回类型
void doSomeT(T t);//T 也可以是参数类型
}
public class TestInt implements TService<Integer> {//实现接口的时候知道需要实现的类型,指定为Integer
@Override
public Integer getT() {
System.out.println("TService<Integer> --- Integer 类型,返回随机数");
return new Random().nextInt(50);
}
@Override
public void doSomeT(Integer integer) {
System.out.println("入参类型 Integer "+integer);
}
}
public class TestString implements TService<String> {//实现接口的时候知道需要实现的类型,指定为String
@Override
public String getT() {
System.out.println("TService<String> ----- String 类型,返回string");
return "string";
}
@Override
public void doSomeT(String s) {
System.out.println("入参类型 String "+s);
}
}
public class TestObject implements TService {
@Override
public Object getT() {
System.out.println("TService ----- 返回Object");
return "Object";
}
@Override
public void doSomeT(Object o) {
System.out.println("入参类型 Object "+o);
}
}
public class TestOther<T extends Parent> implements TService<T> {//实现接口的时候知道不需要实现的类型
@Override
public T getT() {
//根据实际场景使用该方法
return null;
}
@Override
public void doSomeT(T t) {
t.doSome();
System.out.println("入参类型 "+t.getClass().getName());
}
}
public class Child extends Parent {
public String var="-Child";
public final String varFinal="-ChildParentFinal";
public static void staticDoSome(){
System.out.println("Child staticDoSome");
}
@Override
public void doSome(){
System.out.println("Child,doSome"+var);
}
public void showMe(){
this.doSome();
super.doSome();
((Parent)this).doSome();
((GrandParent)this).doSome();
System.out.println("Child this.var--"+this.var);
System.out.println("Child ((Parent)this).var--"+((Parent)this).var);
System.out.println("Child ((GrandParent)this).var--"+((GrandParent)this).var);
System.out.println("Child this.staticDoSome()--");
this.staticDoSome();
System.out.println("Child super.staticDoSome()--");
super.staticDoSome();
}
}
public class Parent extends GrandParent {
public String var="-Parent";
public final String varFinal="-ParentFinal";
public static void staticDoSome(){
System.out.println("Parent staticDoSome");
}
@Override
public void doSome(){
System.out.println("Parent,doSome"+var);
}
}
public class GrandParent {
public String var="-GrandParent";
public final String varFinal="-GrandParentFinal";
public static void staticDoSome(){
System.out.println("GrandParent staticDoSome");
}
public void doSome(){
System.out.println("GrandParent,doSome"+var);
}
}
先看结构,TestInt implements TService<Integer> 实现接口的时候知道需要实现的类型,指定为Integer,所以这里的T就是Integer类型,TestString同理,可以看出泛型接口中定义的T表示当前不知道这个类型是什么,具体是谁看实现,反正放什么类型过来都行,如果不给定具体类型,一律按Object类型处理,就像TestObject那样.但是实际情况往往不止这些,比如,在实现接口的时候依旧不知道该声明声明类型,这时候该如何处理?既然这样,那就依旧声明为T吧,就像TestOther那样,甚至可以自己加上个限制,比如这个T必须是Parent的子类.
接下来,看看这段测试代码,
@Test
public void testTService(){
//声明Integer类型
TService<Integer> tInt=new TestInt();
//声明String 类型
TService<String> tString=new TestString();
//不声明,默认为Object类型
TService tObject=new TestObject();
//声明一个TestOther<>对象
TestOther tOther=new TestOther<>();
//声明一个TestOther<Child>对象
TestOther<Child> tOther2= new TestOther<>();
tInt.doSomeT(10);//参数为Integer
System.out.println(tInt.getT());
tString.doSomeT("showMeString");//参数为String
System.out.println(tString.getT());
tObject.doSomeT("Object");//参数为Object,并出现警告
System.out.println(tObject.getT());
tOther.doSomeT(new Child());//出现警告
tOther.doSomeT(new Parent());//出现警告
// tOther.doSomeT(new GrandParent());//这里会编译器报错,因为 GrandParent 超过了泛型参数的上限
tOther2.doSomeT(new Child());
// tOther2.doSomeT(new Parent());//这里也会报错,参数只能为Child 类型
}
分别声明了参数为Integer,String,Object(不传默认为Object)三种实现,tInt,tString,tObject,(这里插一句,T的类型不能为基本类型比如int,需要封装类型,比如Integer)既然已经声明了类型,就如同List<Integer>中不能存放String类型一般,tInt里面的参数也是Integer类型,doSomeT的参数也是Integer类型,tString同理,但是tObject的doSomeT就有点尴尬了,这里会出现警告,因为编译器也不知道后续操作会不会出现转换异常(当然这个测试中不会出现).
再看另一个情况接口实现的时候不知道T到底是什么类型,但是能肯定的是,那个未知类型将是Parent的子类所以这么声明![]()
,tOther与tOther2,tOther默认泛型为Object,tObject2则不一样,声明时就说好了,这是一个Child对象,其他的恕不接待.所以可以看到
,tOther能接受Child,Parent对象,但是GrandParent对象不行,因为在定义的时候就已经设置了上限,而tOther2上来就已经压缩了上限,只接受Child,如果将tOther的声明改成
TestOther<Parent> tOther2= new TestOther<>();
那么tOther也能接受Parent,当然对Child也是可以的,毕竟Child是Parent子类.
然后看看打印结果:
入参类型 Integer 10
TService<Integer> --- Integer 类型,返回随机数
16
入参类型 String showMeString
TService<String> ----- String 类型,返回string
string
入参类型 Object Object
TService ----- 返回Object
Object
Child,doSome-Child
入参类型 myTest.ex.Child
Parent,doSome-Parent
入参类型 myTest.ex.Parent
Child,doSome-Child
入参类型 myTest.ex.Child
在看看泛型方法,对TestOther稍微添加一些内容,添加 dealStaticSome,dealSome,getListT三个泛型方法,代码如下:
public class TestOther<T extends Parent> implements TService<T> {//实现接口的时候知道不需要实现的类型
@Override
public T getT() {
//根据实际场景使用该方法
return null;
}
@Override
public void doSomeT(T t) {
t.doSome();
System.out.println("入参类型 "+t.getClass().getName());
}
public static <T> void dealStaticSome(T t){
System.out.println(t.getClass().getName());
}
public <T> void dealSome(T t){
System.out.println(t.getClass().getName());
}
public <T extends Number> List<T> getListT(T...ts){
System.out.println("参数类型: "+ts.getClass().getName());
List<T> listT=new ArrayList<T>();
for (T t:ts){
listT.add(t);
}
return listT;
}
}
再看看调用方法
@Test
public void testDealT(){
TestOther<Child> tOther2= new TestOther<>();
TestOther.dealStaticSome("String");//正常
tOther2.dealSome(10);//正常
System.out.println(tOther2.getListT(1,2,3,4,5,6,7,8,9,0));
// tOther2.getListT("string");//这里编译器报错
}
这里同样也定义了一个tOther2对象,T为Child类型,调用三个泛型方法,输出结果如下:
java.lang.String
java.lang.Integer
参数类型: [Ljava.lang.Integer;
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
从这里可以看出,泛型类和泛型方法虽然都定义的是T
![]()
但实际上,泛型方法使用的时候,是不受泛型类中定义T所影响的,可以理解为两个不同的东西,调用泛型方法是,无论String,Integer都能接收,并且输出结果,而由于getListT这个方法对传入参数做了限定
<T extends Number>
所以输入的参数只能为数字类型.
本文探讨了Java中的泛型,通过比较List的不同声明方式,解释了泛型带来的安全性、便利性和表述性。讨论了泛型接口的实现,包括指定类型、未指定类型以及类型约束,并通过实例展示了泛型方法的使用及其与泛型类的区别。
1万+

被折叠的 条评论
为什么被折叠?



