一、泛型(Generics)
为了能够更好的学习容器,我们首先要先来学习一个概念:泛型。
泛型基本概念,泛型是JDK5.0以后增加的新特性。
泛型的本质就是“数据类型的参数化”,处理的数据类型不是固定的,而是可以作为参数传入。我们可以把“泛型”理解为数据类型的一个占位符(类似:形式参数),即告诉编译器,在调用泛型时必须传入实际类型.
参数化类型,白话说就是:
1.把类型当作是参数一样传递。
2. <数据类型> 只能是引用类型。
泛型的好处
在不使用泛型的情况下,我们可以使用Object类型来实现任意的参数类型,但是在使用时需要我们强制进行类型转换。这就要求程序员明确知道实际类型,不然可能引起类型转换错误;但是,在编译期我们无法识别这种错误,只能在运行期发现这种错误。使用泛型的好处就是可以在编译期就识别出这种错误,有了更好的安全性;同时,所有类型转换由编译器完成,在程序员看来都是自动转换的,提高了代码的可读性。
总结一下,就是使用泛型主要是两个好处:
1、代码可读性更好【不用强制转换】
2、程序更加安全【只要编译时期没有警告,运行时期就不会出现ClassCastException异常】
类型擦除
编码时采用泛型写的类型参数,编译器会在编译时去掉,这称之为“类型擦除”。
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。
泛型主要是方便了程序员的代码编写,以及更好的安全性检测。
实时效果反馈
1.泛型是在JDK哪个版本中增加的新特性?
A JDK5.0
B JDK6.0
C JDK7.0
D JDK8.0
2.泛型的本质是什么?
A 数据的参数化
B 对象的参数化
C 数据类型的参数化
D 基本类型的参数化
答案
1=>A 2=>C
泛型类
泛型标记
定义泛型时,一般采用几个标记:E、T、K、V、N、?。他们约定
俗称的含义如下:
泛型类的使用
语法结构
public class 类名<泛型标识符号> { } public class 类名<泛型标识符号,泛型标识符号> { }
示例
public class Generic<T> { private T flag; public void setFlag(T flag){ this.flag = flag; } public T getFlag(){ return this.flag; } }
public class Test { public static void main(String[] args) { //创建对象时,指定泛型具体类型。 Generic<String> generic = new Generic<>(); generic.setFlag("admin"); String flag = generic.getFlag(); System.out.println(flag); //创建对象时,指定泛型具体类型。 Generic<Integer> generic1 = new Generic<>(); generic1.setFlag(100); Integer flag1 = generic1.getFlag(); System.out.println(flag1); } }
实时效果反馈
1.如下哪个选项是正确定义泛型类的语法
A public class <泛型标识符号> 类名
B public <泛型标识符号> class 类名
C <泛型标识符号> public class 类名
D public class 类名<泛型标识符号>
答案
1=>D
泛型接口
泛型接口和泛型类的声明方式一致。
泛型接口的使用
语法结构
public interface 接口名<泛型标识符号> { } public interface 接口名<泛型标识符号,泛型标识符号>{ }
示例
public interface IGeneric<T> { T getName(T name); } //在实现接口时传递具体数据类型 public class IgenericImpl implements Igeneric<String> { @Override public String getName(String name) { return name; } } //在实现接口时仍然使用泛型作为数据类型 public class IGenericImpl2<T> implements IGeneric<T>{ @Override public T getName(T name) { return name; } } public class Test { public static void main(String[] args) { IGeneric<String> igeneric= new IGenericImpl(); String name = igeneric.getName("old"); System.out.println(name); IGeneric<String> igeneric1 = new IGenericImpl2<>(); String name1 = igeneric1.getName("it"); System.out.println(name1); } }
实时效果反馈
1.如下哪个选项是正确定义泛型接口的语法
A public interface<泛型标识符号> 接口名
B public <泛型标识符号> interface 接口名
C <泛型标识符号> public interface 接口名
D public interface 接口名<泛型标识符号>
答案
1=>D
泛型方法
类上定义的泛型,在方法中也可以使用。但是,我们经常需要仅仅在某一个方法上使用泛型,这时候可以使用泛型方法。
调用泛型方法时,不需要像泛型类那样告诉编译器是什么类型,编译器可以自动推断出类型
泛型方法的使用
非静态方法
非静态方法可以使用泛型类中所定义的泛型,也可以将泛型定义在方法上。
语法结构
//无返回值方法 public <泛型标识符号> void getName(泛型标识符号name) { } //有返回值方法 public <泛型标识符号> 泛型标识符号 getName(泛型标识符号 name) { }
示例
public class MethodGeneric { public <T> void setName(T name){ System.out.println(name); } public <T> T getAge(T age){ return age; } }
public class Test2 { public static void main(String[] args) { MethodGeneric methodGeneric = new MethodGeneric(); methodGeneric.setName("old"); Integer age = methodGeneric.getAge(123); System.out.println(age); }
静态方法
静态方法中使用泛型时有一种情况需要注意一下,那就是静态方法无法访问类上定义的泛型,所以必须要将泛型定义在方法上。
语法结构
//无返回值静态方法 public static <泛型标识符号> void setName(泛型标识符号 name){ } //有返回值静态方法 public static <泛型标识符号> 泛型表示符号getName(泛型标识符号 name){ }
示例
public class MethodGeneric { public static <T> void setFlag(T flag){ System.out.println(flag); } public static <T> T getFlag(T flag){ return flag; } }
public class Test4 { public static void main(String[] args) { MethodGeneric.setFlag("old"); Integer flag1 = MethodGeneric.getFlag(123123); System.out.println(flag1); } }
实时效果反馈
1.如下哪个选项是正确定义泛型方法的语法
A <泛型标识符号> public void getName(泛型标识符号 name)
B public void <泛型标识符号> getName(泛型标识符号 name)
C public <泛型标识符号> void getName(泛型标识符号 name)
D public void getName <泛型标识符号>(泛型标识符号 name)
答案
1=>C
泛型方法与可变参数
在泛型方法中,泛型也可以定义可变参数类型。
语法结构
public <泛型标识符号> void showMsg(泛型标识符号... agrs){ }
示例
public class MethodGeneric { public <T> void method(T...args){ for(T t:args){ System.out.println(t); } } } public class Test5 { public static void main(String[] args) { MethodGeneric methodGeneric = new MethodGeneric(); String[] arr = new String[]{"a","b","c"}; Integer[] arr2 = new Integer[]{1,2,3}; methodGeneric.method(arr); methodGeneric.method(arr2); } }
实时效果反馈
1.如下哪个选项是正确的在可变参数中使用泛型
A public <泛型标识符号> void showMsg(泛型标识符号... agrs)
B public void showMsg(<泛型标识符号>... agrs)
C public <泛型标识符号> void showMsg(<泛型标识符号>... agrs)
D public <泛型标识符号> void showMsg(Object... agrs)
答案
1=>A
泛型中的通配符
无界通配符
“?”表示类型通配符,用于代替具体的类型。它只能在“<>”中使用。可以解决当具体类型不确定的问题。
语法结构
public void showFlag(Generic<?> generic){ }
示例
public class Generic<T> { private T flag; public void setFlag(T flag){ this.flag = flag; } public T getFlag(){ return this.flag; } } public class ShowMsg { public void showFlag(Generic<?> generic){ System.out.println(generic.getFlag()); } } public class Test3 { public static void main(String[] args) { ShowMsg showMsg = new ShowMsg(); Generic<Integer> generic = new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1 = new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); Generic<String> generic2 = new Generic<>(); generic2.setFlag("old"); showMsg.showFlag(generic2); } }
实时效果反馈
1.在泛型中,无界通配符使用什么符号来表示?
A !
B ?
C #
D *
答案
1=>B
统配符的上下限定
统配符的上限限定
对通配符的上限的限定:<? extends 类型> ?实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型;
语法结构
public void showFlag(Generic<? extends Number> generic){ }
示例
public class ShowMsg { public void showFlag(Generic<? extends Number> generic){ System.out.println(generic.getFlag()); } } public class Test4 { public static void main(String[] args) { ShowMsg showMsg = new ShowMsg(); Generic<Integer> generic = new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1 = new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); } }
实时效果反馈
1.对通配符的上限的限定是指
A 实际类型只能是上限限定中所约定的类型;
B 实际类型只能是上限限定中所约定类型的子类型;
C 实际类型可以是上限限定中所约定的类型,也可以是约定类型的子类型;
D 实际类型可以是上限限定中所约定的类型,也可以是约定类型的父类型;
答案
1=>C
通配符的下限限定
对通配符的下限的限定:<? super 类型> ?实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型;
语法结构
public void showFlag(Generic<? super Integer> generic){ }
示例
public class ShowMsg { public void showFlag(Generic<? super Integer> generic){ System.out.println(generic.getFlag()); } } public class Test6 { public static void main(String[] args) { ShowMsg showMsg = new ShowMsg(); Generic<Integer> generic = new Generic<>(); generic.setFlag(20); showMsg.showFlag(generic); Generic<Number> generic1 = new Generic<>(); generic1.setFlag(50); showMsg.showFlag(generic1); } }
实时效果反馈
1.对通配符的下限的限定是指
A 实际类型只能是下限限定中所约定的类型;
B 实际类型只能是下限限定中所约定类型的子类型;
C 实际类型可以是下限限定中所约定的类型,也可以是约定类型的子类型;
D 实际类型可以是下限限定中所约定的类型,也可以是约定类型的父类型;
答案
1=>D
泛型局限性和常见错误
泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息。 类型参数在编译后会被替换成Object,运行时虚拟机并不知道泛型。因此,使用泛型时,如下几种情况是错的:
实时效果反馈
1.如下哪个选项是错误的使用泛型?
A Generic
B Generic
C Generic
D Generic
答案
1=>D
二、容器介绍
容器简介
容器,是用来容纳物体、管理物体。生活中,我们会用到各种各样的容器。如锅碗瓢盆、箱子和包等。
程序中的“容器”也有类似的功能,用来容纳和管理数据。比如,如下新闻网站的新闻列表、教育网站的课程列表就是用“容器”来管理:
开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。 我们一般通过“容器”来容纳和管理数据。事实上,我们前面所学的数组就是一种容器,可以在其中
放置对象或基本类型数据。
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从查询效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:我们在一个用户管理系统中,要把今天注册的所有用户取出来,那么这样的用户有多少个?我们在写程序时是无法确定的。因此,在这里就不能使用数组。
基于数组并不能满足我们对于“管理和组织数据的需求”,所以我们需要一种更强大、更灵活、容量随时可扩的容器来装载我们的对象。 这就是我们今天要学习的容器,也叫集合(Collection)。
实时效果反馈
1.Java中容器的作用是什么?
A 容纳数据
B 处理数据
C 生产数据
D 销毁数据
答案
1=>A
容器的结构
结构图
单例集合
双例集合
实时效果反馈
1.如下哪个接口不是容器接口?
A List
B Set
C Map
D Comparable
答案
1=>D
单例集合
Collection接口介绍
Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
Collection接口中定义的方法
实时效果反馈
1.如下哪个接口不是Collection接口的子接口?
A List
B Set
C Map
D Queue
答案
1=>C