苦尽甘来 一个月学通JavaWeb(九 Java基础加强)

 

夜光序言:

去爱一个能给你正能量的人~~

 

 

正文:泛型

 

 

1 数组与集合

Java中可以定义任意类型的属性,例如String[]中存放的就是String类型的数据,我们称之为持有String类型的数组。但1.5之前时,Java的集合类却只能持有Object类型,1.5时添加了泛型的概念,泛型允许Java创建持有任意类型的集合对象,例如:new ArrayList<String>()表示这个ArrayList中只能持有String类型的对象。

 

2 类型变量(参数)

具有一个或多个类型参数的类就是泛型类。

泛型类都至少有一个类型变量,你需要在创建泛型类对象时给类型变量赋值。当然,你要给类型变量赋的值必须是一个类型!

ArrayList<String> arr = new ArrayList<String>();

 

其中String就是给ArrayList类的类型变量赋值。

 

在ArrayList类中所有使用类型变量的地方都会被String所替换。

 

例如:boolean add(E e),其中e的类型就是变量,它会被String替换,最终变成boolean add(String e)。

E get(int index)方法中返回值的类型为变量,它也会被String替换,最终变成String get(int index)。

看看下面这个案例~~:夜光

 

 

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.junit.Test;

/**
 * 夜光
 * 泛型在集合上的使用
 * @author Genius
 *
 */
public class TestDemo02 {

	//在map上使用泛型
	@Test
	public void testMap() {
		Map<String,String> map = new HashMap<String,String>();
		map.put("aaa", "111");
		map.put("bbb", "222");
		map.put("ccc", "333");
		//遍历map 有几种遍历方式 两种
		// 1、获取所有的key,通过key得到value 使用get方法
		// 2、获取key和value的关系
		//使用第一种方式遍历
		//获取所有的key
		Set<String> sets = map.keySet();
		//遍历所有key返回的set
		for (String key : sets) {
			//通过key得到value
			String value = map.get(key);
			System.out.println(key+" : "+value);
		}
		
		System.out.println("==============");
		//得到key和value的关系
		Set<Entry<String, String>> sets1 = map.entrySet();
		//遍历sets1
		for (Entry<String, String> entry : sets1) {
			//entry是key和value关系
			String keyv = entry.getKey();
			String valuev = entry.getValue();
			System.out.println(keyv+" : "+valuev);
		}
	}
	
	//泛型使用set集合上
	@Test
	public void testSet() {
		Set<String> set = new HashSet<String>();
		set.add("www");
		set.add("qqq");
		set.add("zzz");
		//set.add("qqq");
		//遍历set 有几种方式  两种
		//迭代器  增强for
		//使用增强for遍历
		for (String s2 : set) {
			System.out.println(s2);
		}
		System.out.println("=================");
		//使用迭代器遍历
		Iterator<String> it1 = set.iterator();
		while(it1.hasNext()) {
			System.out.println(it1.next());
		}
	}
	
	//泛型在list上的使用
	@Test
	public void testList() {
		List<String> list = new ArrayList<String>();
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		//遍历list集合 有几种方式  三种
		//普通for循环  迭代器  增强for
		//普通for循环
		for(int i=0;i<list.size();i++) {
			String s = list.get(i);
			System.out.println(s);
		}
		System.out.println("=================");
		//使用增强for
		for (String s1 : list) {
			System.out.println(s1);
		}
		System.out.println("=================");
		//使用迭代器遍历
		Iterator<String> it = list.iterator();
		while(it.hasNext()) {
			System.out.println(it.next());
		}
	}
}

3 泛型的好处

将运行期遇到的问题转移到了编译期。

 

例如在1.4时,ArrayList类的add()方法参数还是Object类型,当然get()方法的返回值类型也是Object。这就说明使用get()方法获返回值后,你还需要强转。

 

错误的强转可能会出现ClassCastException。

 

ArrayList list = new ArrayList();

list.add(“hello”);

Integer i = (Integer)list.get(0);//抛出异常

 

这个问题在有了泛型之后就不会再有了。

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(“hello”);//编译出错~~

Integer i = (Integer)list.get(0);

 

夜光:  很明显,是泛型把只能在运行时才能找到的错误推向了编译期!这就是泛型的优点~~~~~~

定义泛型类(接口)

 

1 自定义泛型类的语法

自定义泛型类的语法基本与定义正常的法一样:

public class A<T> {}

定义泛型类时,需要在类名后面给出一对尖括号,在尖括号中给出1~N个类型变量。

 

2 泛型类中使用类型变量

用户在使用泛型类时,需要为类型变量赋值。例如:new A<String>()。

在泛型类中可以使用类型变量还是挺有趣的~~

public class A<T> {

    private T t;

    public A(T t) {

        this.t = t;

}

public T get() {

    return t;

}

}

当用户创建A类对象时,就会给A类的类型变量T赋值,例如:new A<String>(),这说明在A类中所有的T都会被String替换。

public class A {

    private String t;

    public A(String t) {

        this.t = t;

}

public String get() {

    return t;

}

}

 

3 类型变量的限制

上例中,A类中定义了T类型的属性t,这个属性t的类型是一个变量,无法确定的,因为你在定义A类时,根本就不知道用户会传递什么类型给T,所以你也不能调用属性t的方法,因为你不知道t的类型,就不知道它有什么方法。但是,我们知道任何类都是Object类的子类,那么就说明你可以调用属性t的Object中存在的方法。

注意,你不能使用T类型的构造器:new T(),因为你不能确定t的类型,那么也就不知道它有什么样的构造器,甚至是否有public构造器,所以不能创建。

相同的道理,也不能创建T类型的属性,例如:new T[10],这也是不行的!

还有一点,在泛型类中,static方法中不能使用类型变量T。

 

继承(实现)泛型类(接口)

 

1 继承泛型类之一

如果当前类是泛型类,那么在继承(实现)泛型类(接口)时,可以把自己的类型变量传递给父类(接口)。

public class ArrayList<E> implements List<E> {

    …

}

可以这样来理解,其中public class ArrayList<E>中的<E>表示定义了一个泛型类,其中E是定义的类型变量,而implements List<E>中的<E>表示给List接口传递<E>,这是在使用变量类型E,而不是在定义了。

ArrayList<String> arr = new ArrayList<String>();

在用户创建ArrayList类对象时,传递给ArrayList类中的E的值是String,那么ArrayList会把这个String再传递给List中的E。

夜光:你可能会想,为什么一定要给父类或接口传递类型变量。那我会问你,实现类不给List接口中的E来赋值,谁来给它赋值?难道你想new List<String>()这样赋值么?接口是不能被实例化的,你只能让实现类来赋值,你不赋值,接口中的get()方法一直返回E类型~~~~~~

 

2 继承泛型类之二

泛型类的子类不一定必须为泛型类,也可以是非泛型类。这时因为子类没有类型变量可以传递给父类,那么也就能传递给父类类型常量了。

public class String implements Comparable<String> {

    public int compareTo(String other) {…}

}

 

  这时,在重写父类中方法时,所有的类型变量都是String了。

 

泛型方法

 

1 泛型方法定义

泛型类是说明这个类有类型变量,在创建这个类对象时需要给类型变量赋值。泛型方法是说明这个方法有类型变量,在调用这个方法时需要给类型变量赋值。

public <T> T get(T[] ts, int index) {

  return ts[index];

}

注意,在返回值前面定义类型变量。

get()方法是一个泛型方法,它有一个类型变量T,这说明在调用get()方法时需要给get()方法的T赋值。

如果要定义一个有意义的泛型方法,那么:

  1. 参数需要使用类型变量;
  2. 返回值需要使用类型变量。

所以,通常在调用泛型方法时,只需要传递参数就可以了,例如:

String[] strs = …

String s = o.get(strs, 0);

上面代码中给get()方法的类型变量T赋值为String,因为传递的参数为String数组,所以就是给T赋值为String。

当然,也可以显示给出类型变量的值:o.<String>get(strs,0),在点后面,方法名前面给出类型值,但一般人不会这么打代码。

 

泛型的边界

编译期状态:编译期状态,例如内部类!内部类就是只有编译器知道,而JVM不知道什么叫内部类!

1 泛型的擦除

泛型其实是编译期状态,即JVM不知道什么是泛型,在JVM那里所有类都是正常的类。没有类型变量。一切的一切都是编译器干的好事儿~~~

其实List的get()方法返回值还是Object,只是编译器帮我们添加了强转的语句。但这个强转一定不会出现问题。因为本来add()方法添加元素已经限制过了,那么在get()时,一定不会出现强转的问题。

也就是说,在ArrayList类中持有的还是Object类型,而不是我们指定的类型。当然,就算是JVM没有泛型,但编译器会帮我们完成这些问题,我们就可以当泛型真的存在。

 

2 泛型边界限定的类型值的范围

通常我们看到的泛型类都没有边界限定,也就是说可以给泛型类的类型变量赋任意类型的值(当然基本类型是不可以的)。

java允许给类型变量指定边界,这样用户在给类型变量赋值时就必须在边界之内。

public class A<T extends Number> {}表示用户可以给T赋值为Number或Number的子类型。例如:new A<Integer>()这是可以的,但new A<String>()是不可以的。

 

通配符

 

1 通配符的作用

Object[] objs = new String[10];

objs[0] = new Integer(100);

上面代码编译是可以通过的,但在运行时会出现ArrayStoreException。因为objs数组真实的身份是String[],向String[]数组中存放Integer对象当然是不行的。

ArrayList<Object> list = new ArrayList<String>();

list.add(new Integer(100);

上面代码在第一行位置编译失败,因为泛型根本就不让把ArrayList<String>赋值给ArrayList<Object>,对于ArrayList<Object>而言,只能赋值ArrayList<Object>,其他的什么都不能赋值。

这也说明一个问题:

public static void printList(List<Object> list) {…}

调用printList()方法只能传递给它List<Object>类型的参数,而不能传递List<String>,或者List<Integer>,这说明我们的printList()方法有很多的限制,不够通用~~~~

 

你可能会想我再重载几次printList()方法吧,但这是行不通的~~

 

 

public static void printList(List<Object> list) {…}

public static void printList(List<String> list) {…}

因为JVM不知道什么是泛型,这两个方法在到了JVM那里时都是会把泛型参数擦除,这两个方法就是相同的方法了,擦除之后即:

public static void printList(List list) {…}

public static void printList(List list) {…}

当然JVM不可能看到这样的代码,因为编译器不能让你编译通过~~

处理这个问题需要使用通配符~~

 

2 子类型通配符

public static void printList(List<? extends Person> list) {}

这回可以传递给printList()方法List<Student>,以及List<Teacher>参数了。只要类型参数为Person,或者是Person子类型就都可以。

你可以这样来理解通配符,通配符表示“不知道”的意思。即一个问号~~

但子类型通配符还是知道一些信息的,它只知道用户转递的类型参数一定是Person的子类型。虽然使用了通配符之后printList()方法更加通用了,但是这也是要付出一些代价的。因为不知道List中类型参数的真实类型,所以就不能调用list的add()方法了。你可能会想add(new Student())应该是可以的,但如果List是List<Teacher>呢,你怎么向这样的List添加Student呢?再想一想,add()方法已经作废了,什么都传递不了。

 

3 父类型通配符

public static void printList(List<? super Student> list) {…}

可以传递给printList()方法List<Student>,以及List<Person>,甚至List<Object>也是可以的。只要传递给printList()方法的List类型参数为Student,或者Student的父类型就是可以的。

你现在可以向list中添加Student类型的参数,例如:list.add(new Student())。因为用户提供的参数List<Student>、List<Person>、List<Object>,无论哪一种类型,对于list.add(new Student())都是合法的。

但是,现在我们不知道list的get()方法返回的是什么了!因为用户传递的可能是List<Student>、List<Person>、List<Object>,如果我们用Student s = list.get(0),那么如果list其实是一个List<Person>岂不是出错了!没错,只能使用Object来接收list.get(0)的返回值了。

 

4 无界通配符

所谓无界通配符,即List<?>,对通配符没有限定。你可以给List<?>赋任意的值,但是,你能使用这样的list干什么呢?也不能add(),也只能使用Object来接收get()方法返回值。

所以,通常List<?>只能表示你在使用泛型而已!编译器会认为List<?>比List更加优雅一些!你可能也看出来了,泛型还真是很菜么??这也是没有办法中的办法,现在的泛型是迁移性兼容的一种版本而已!Java设计者不敢让JVM知道泛型的存在,原因是为了兼容1.4之前的版本。当所有人都在使用1.5以上版本的JDK后,Java的泛型可能就不会再是编译期状态了。

 

1.5新特性【夜光:这个是学习的别人的~~】

自动装箱拆箱

 

1 什么是自动装箱拆箱

在1.5之后,Java允许把基本类型与其对应的包装器类型之间自动相互转换。例如:Integer i = 100,把int类型的100直接给了Integer类型的变量i,这就是自动装箱。int a = new Integer(100),这是自动拆箱。

Object o = 100;//其实是把100自动装箱为Integer,即Object o = Integer.valueOf(100)。

int a = (Integer)o;//其实是把o强转为Integer后,自动拆箱为int,即int a=((Integer)o).intValue()。

 

2 Integer.valueOf()与Integer内部缓存

我们已经知道自动装箱使用的是Integer.valueOf()方法,但我们要了解一下,其实valueOf()方法会使用Integer类内部的缓存来获取Integer对象。

Integer类的内部缓存了-128~127之间的256个Integer对象,如果valueOf()方法需要把这个范围之内的整数转换成Integer对象时,valueOf()方法不会去new对象,而是从缓存中直接获取,这就会导致valueOf(100)两次,都是从缓存中获取的同一个Integer对象!

Integer i1 = Integer.valueOf(100);

Integer i2 = Integer.valueOf(100);

boolean b = i1 == i2;//结果为true

 

相同的道理:

Integer i1 = 100;

Integer i2 = 100;

boolean b = i1 == i2;//结果为true

 

但是:

Integer i1 = 200;

Integer i2 = 200;

boolean b = i1 == i2;//结果为false

 

  这是因为200不在Integer内部的缓存之内,所以这时valueOf()方法会new一个Integer对象。每次valueOf(200)都会创建一个新的Integer对象,所以才会是false。

 

==========================================================================

 

 

夜光:我们学习的核心点

增强for

 

1 增强for循环概念

可以循环遍历数组或者集合类。

 

2 增强for循环的语法格式

for(元素类型 e : 数组或集合对象) {

}

增强for每循环一次,都会把数组或集合中的一个元素赋值给e,从头开始遍历,直到最后一个元素。

 

3 增强for的优缺点

只能从头到尾的遍历数组或集合,而不能只遍历部分。

在遍历List或数组时,不能获取当前元素下标。

增强for使用便简单,这是它唯一的优点了。

增强for比使用迭代器方便一点~~~~

 

4 增强for与Iterable接口

  任何实现了Iterable接口的类,都可以使用增强for来遍历。

 

静态导入(额~~("▔□▔),有点鸡肋)

 

1 什么是静态导入

静态导入也需要使用import关键字;

静态导入后,在调用静态方法,以及使用静态属性时就可以不再给出类名了,例如向控制台打印时可以把System.out.println()写成out.println();System.exit(0)写成exit(0)。

 

2 静态导入的语法格式

import static 包名.类名.静态方法名;

import static 包名.类名.静态属性名;

import static 包名.类名.*;

 

3 静态导入真是鸡肋啊

嘿哈~~不建议使用

使用静态导入,使代码可读性降低~~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值