Java编程基础倒数第二篇,感谢没有放弃的自己。
学习笔记参考书籍《Java编程基础》主编 张焕生。本书内容比较适合没有什么基础的入门小白,学完一章还有习题,比较适合初学者。
自律、积极、勤奋以及先从一个怎么样都不可能不会实现的小目标开始。
本文已收录于[ 专栏 ]
《Java入门》系列
前面[ 章节 ]:
Java学习笔记十七——集合类详细总结各自对比
✌ Java必备基础十六——三万字总结输入与输出流
Java必备基础十五——万字长文总结异常处理基本知识点
Java必备基础十四——内部类详细知识点归纳
Java必备基础十三——接口详细知识点归纳
✨Java必备基础十二——抽象类
✌Java必备基础十一——多态详解以及多态原则
Java必备基础十——类的继承详解
Java必备基础九——包装类
Java必备基础八——Math类、Random类、日期时间类
Java必备基础七——字符串的基本常用方法
Java必备基础六——一维数组
Java必备基础五——类的封装
Java必备基础四——成员变量和局部变量
Java必备基础三——方法的声明与调用,递归
Java必备基础二——类和对象
Java必备基础一——Java语言基础
在没有泛型之前,集合中加入特定的对象时,就会被当成Object类型。当从集合中取出对象后,需要进行强制类型转换如何打印相应的对象。这种操作的弊端很容易引起异常,并且代码臃肿。
举个简单的被说了无数遍的例子:
public class Example1_1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add(1);
for (int i=0;i<arrayList.size();i++){
String s = (String)arrayList.get(i) ;
System.out.println(s);
}
}
}
输出的结果是:ClassCastException异常
a
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.chapter8.Example1_1.main(Example1_1.java:17)
集合类ArrayList
添加了String
类型和Integer
类型,但是输出时把这两个类型强转了,因此程序崩溃了。为了解决这样的问题 ,引入泛型。
引入泛型的集合可以记住元素类型,在编译时检查元素类型,如果集合中添加了不满足类型的对象时编译器就会提示错误。可以看到下图中定义了泛型元素为String
类型,添加int
类型的对象时编译器提示要求String
类型对象。
需要额外注意的一点是:泛型的集合可以记住元素类型,在编译时检查元素类型,举个例子理解这句话:
public class Example1_1 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList.add("2");
Class classStringArrayList = arrayList.getClass();
Class classStringArrayList1 = arrayList1.getClass();
if(classStringArrayList.equals(classStringArrayList1)){
System.out.println("类型相同");
}
}
}
可以猜测一下程序的输出结果,程序会打印出类型相同。虽然两个类添加了不同类型的泛型,但是这两个类依旧是相同的类别。于是我们可以大胆的猜测一下:泛型只在编译阶段有效,在检验泛型结果后,程序会将泛型的相关信息擦除,泛型信息不会进入到运行时阶段。
在由来中已经简单提到了没有泛型时的缺点,那么有了泛型时,缺点就是优点:
List<String> list = new LinkedList<>();
Set<String> set = new HashSet<>();
Map<String,String> map = new HashMap<>();
泛型对于集合类非常重要,在集合中引入泛型能够提供编译时的类型安全,并且从集合中取出元素后不必再强制转换,简化了程序代码。
public class Demo1 {
public static void main(String[] args) {
List<String> ar=new ArrayList<>();
Map<Integer,String> map = new HashMap<>();
ar.add("a");
ar.add("b");
map.put(1,"c");
map.put(2,"d");
System.out.println("ar的集合元素为:"+ar);
//遍历Map集合中的元素的一种方法
Iterator<Map.Entry<Integer,String>> map1=map.entrySet().iterator();
while (map1.hasNext()){
Map.Entry<Integer,String> next = map1.next();
System.out.println(next.getKey()+""+next.getValue());
}
}
}
输出的结果为:
ar的集合元素为:[a, b]
1c
2d
class 类名<泛型标识>{
private 泛型标识 标识名
}
/**
* @param T代表泛型标识,也可以写成E、K,编程人自己定义
* 在实例化泛型类型时,必须指定T的具体类型
*/
public class Example1_1<T>{
/**
* key这个成员变量的类型为T,T的类型由外部决定
*/
private T key;
/**
* @param key 泛型构造方法形参key的类型为T,由外部指定
*/
public Example1_1(T key){
//泛型构造方法形参key的类型也为T,T的类型也由外部决定
this.key = key;
}
/**
* @return 泛型普通方法getKey的返回类型为T,T的类型由外部指定
*/
public T getKey() {
return key;
}
public static void main(String[] args) {
//泛型的类型参数只能是引用类型,并且传入的实参类型必须要与定义的泛型对象的类型相同
Example1_1<Integer> exampleInt= new Example1_1<>(0);
//错误,定义泛型对象类型要和传入的实参类型相同
Example1_1<Integer> exampleInt1= new Example1_1<String>("1");
Example1_1<String> exampleString = new Example1_1<>("2");
System.out.println("exampleInt的key值:"+exampleInt.getKey());
System.out.println("exampleString的key值:"+exampleString.getKey());
//下面这些写法都是正确的,定义的泛型类不用都传入泛型的实参,实际上的泛型类可以被定义成任何类型,如果有定义,就会像上面一下做出限制
Example1_1 a = new Example1_1(2);
Example1_1 b = new Example1_1("hello");
Example1_1 c = new Example1_1(false);
}
}
输出结果为:
exampleInt的key值:0
exampleString的key值:2
//定义一个泛型接口
public interface Example1_2<T> {
T next();
}
//定义一个泛型接口Example1_2
public interface Example1_2<T> {
T fly();
}
//接下来实现这个接口
/**
* 错误写法:不声明泛型编译器会报错无法找到T
*/
class Bird implements Example1_2<T>{
@Override
public T fly() {
return null;
}
}
/**
* @param 正确写法1,未传入泛型实参,
* 与泛型类的定义相同,在声明类的时候,需将泛型的声明一起加入到类中
*/
class Bird1<T> implements Example1_2<T>{
@Override
public T fly() {
return null;
}
}
/**
* 正确写法2,传入泛型实参:
* 可以看到,当实现接口时确定了泛型的类型,则使用使用到泛型的地方都要保持一致的类型
*
*/
class Bird2 implements Example1_2<String>{
@Override
public String fly() {
return null;
}
}
/**
* @param tClass 传入的泛型实参
* @param 返回值为T类型
* @return
* 说明:
*
*/
public <T>T genericMethod(Class<T> tClass) throws Exception{
T instance = tClass.newInstance();//newInstance方法抛出了异常
return instance;
}
看到上面的例子说明一下泛型的方法:
,可以李佳节为声明此方法的泛型方法
的方法才是泛型方法✨✨✨下面这个例子概括了很多正确的泛型方法和不正确的泛型方法:自己在电脑上敲一遍,泛型方法就大致ok了。
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
/**
* @return 这不是一个泛型方法,其返回值是声明泛型类时声明过的方法
*/
public T getKey() {
return key;
}
/**
* 方法有问题,因为在类的的声明中并没有声明泛型E
* 所以在使用E做形参和返回值的类型时,编译器无法识别
*/
public E setKey(E key) {
return key;
}
/**
* @param generic 泛型类引用
* @param 泛型类类型
* @return 返回值
* 这是泛型方法
* public和返回值之间的 必须要有,同时声明了返回值T
* 泛型的数量可以为任意多个
*/
public <T> T showKeyName(Generic<T> generic) {
return generic.key;
}
/**
* @param generic 不是泛型方法,是形参,类型是
*/
public void showKeyName1(Generic<T> generic) {
}
/**
* @param generic 不是泛型方法 ?是实参,赋值的时候再赋予
*/
public void showKeyName2(Generic<?> generic) {
}
/**
* 这个方法有问题,因为泛型类型E未被声明
*
* @param generic Generic类的引用对象,其类型为E
* @param 声明泛型方法
* @return 返回值类型未T
*/
public <T> T showKeyName3(Generic<E> generic) {
}
/**
* 是正确的泛型方法
* 声明了一个泛型方法,无返回值,使用泛型E,E可为任意类型
* 由于泛型方法在声明的时候会声明泛型,因此即使在泛型类中没有声明泛型,编译器也坑正确识别泛型方法中的泛型>
*/
public <E> void showKeyName3(E t) {
}
/**
* 声明了一个泛型方法,无返回值,使用泛型,T可为任意类型
* 注意这里的T与泛型类中定义的T不是一个类型
*/
public <T> void showKeyName4(T t) {
}
/**
* 声明了一个泛型方法,无返回值,使用泛型,T可为多个任意类型
* showKeyName4的输入参数可以为任意个,任意种
*/
public static <T> void showKeyName4(T... args) {
for (T t :args){
System.out.println(t);
}
}
/**
* 对于需要使用泛型的静态方法而言,需要添加额外的泛型声明
* 即使静态方法中使用过泛型类中的声明也不可以。
* T t和要保持一致
*/
public static<T> void showKeyName5(T t) {
}
}
,除了非静态的void修饰的无返回值方法外,其它都要声明先来看一个例子:
class Bird2<T> {
public static void showValue(Bird2<Integer> bird2) {
System.out.println("这是Integer类型");
}
public static void main(String[] args) {
Bird2<Number> bird2 = new Bird2();
Bird2<Integer> bird1 = new Bird2();
showValue(bird2);
showValue(bird1);//这里会报错
}
}
上面的 showValue(bird1)
会报错:解决办法就是:将原来的Integer
类型修改为?
,问题就可以解决。
public static void showValue(Bird2<?> bird2) {
System.out.println(bird2.fly());
类型通配符一般使用?
代替具体的类型实参,此处的?是类型实参,不是形参。可以解决诸如上面的当具体类型不确定的时候,使用?
代替,在使用的时候给这个类型实参赋真正的类型。