您的位置:首页 > 职场人生

黑马程序员 Java高新技术 四

2014-01-11 21:38 405 查看
----------------------
ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------

Java高新技术 四

泛型

1.泛型是在JDK1.5以后出现的新特性。泛型是用于解决安全问题的,是一个安全机制。

2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据。

3.泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。

4.由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。

泛型的好处

1.使用泛型集合,可将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象;这样就将运行时期出现的问题ClassCastException转移到了编译时期,方便与程序员解决问题,让运行时期问题减少,提高安全性。

2.当从集合中获取一个对象时,编译器也可知道这个对象的类型,不需要对对象进行强制转化,避免了强制转换的麻烦,这样更方便。

格式:

通过<>来定义要操作的引用数据类型

如:ArrayList<String> -------- 来定义要存入集合中的元素指定为String类型

有关泛型的术语

1.ArrayList<E>整个称为泛型类型

2.ArrayList<E>中的E称为类型变量或类型参数

3.整个ArrayList<Integer>称为参数化类型

4.ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数

5.ArrayList<Integer>中的<>称为typeof

6.ArrayList称为原始类型

参数化:parametered,已经将参数变为实际类型的状态。

在使用java提供的对象时,何时写泛型?

通常在集合框架中很常见,只要见到<>就要定义泛型,其实<>就是用来接收类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。

关于参数化类型的几点说明:

1.参数化类型与原始类型的兼容性

第一、参数化类型可引用一个原始类型的对象,编译只是报警告,能不能通过编译,是编译器说了算。

如:Collection<String> coll = new Date();

第二、原始类型可引用一个参数化类型的对象,编译报告警告

如:Collection coll = new Vector<String>();

原来的方法接受一个集合参数,新类型也要能传进去。

2.参数的类型不考虑类型参数的继承关系:

Vector<String> v = new Vector<Objec>();//错误的

不写Object没错,写了就是明知故犯

Vector<Objec> v = new Vector<String>();//错误的

3.在创建数组实例时,数组的元素不能使用参数化的类型

如:Vector<Integer> v[] = newVector<Integer>[10];//错误的

代码演示:

ArrayList<String> al = new ArrayList<String>();
al.add("25");
al.add("b");
System.out.println(al.get(1));

ArrayList<Integer> at = new ArrayList<Integer>();
at.add(23);
at.add(3);
System.out.println(at.get(1));
//编译器生成的字节码会去掉泛型的类型信息
System.out.println((al.getClass() == at.getClass()) +
"-->" + at.getClass().getName());

//at.add("ab")-->报错,存储的应为Integer类型
//反射方式,由于编译器生成的字节码会去掉泛型的类型信息,
//所以用反射可跳过编译器,存入任何类型
at.getClass().getMethod("add",Object.class).invoke(at,"abcd");
at.getClass().getMethod("add",Object.class).invoke(at,5);
System.out.println("反射方式:" + at.get(3));
System.out.println("反射方式:" + at.get(4));

//反射方式获得new String(new StringBuffer("abc"));
Constructor<String> cons = String.class.getConstructor(StringBuffer.class);
String st = cons.newInstance(new StringBuffer("abc"));
System.out.println(st);


泛型中的通配符

当传入的类型不确定时,可以使用通配符?

1.使用?通配符可引用其他各种类型化的类型,通配符的变量主要用作引用,也可调用与参数化无关的方法,但不能调用与参数化有关的方法。

2.可对通配符变量赋任意值:

如:Collection<?> coll ---> coll = newHashSet<Date>();

如:

public static void printObj(Collection<?> coll){

//coll.add(1);是错误的,如果传入的是String类型,就不符合了

for(Object obj : coll){

System.out.println(obj);

}

}

代码演示:

class GenerticDemo
{
public static void main(String[] args)
{
ArrayList<String> p = new ArrayList<String>();
p.add("per20");
p.add("per11");
p.add("per52");
print(p);
ArrayList<Integer> s = new ArrayList<Integer>();
s.add(new Integer(4));
s.add(new Integer(7));
s.add(new Integer(1));
print(s);
}

public static void print(ArrayList<?> al) {
Iterator<?> it = al.listIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}


通配符的扩展------泛型的限定:

对于一个范围内的一类事物,可以通过泛型限定的方式定义,有两种方式:

1.? extends E:可接收E类型或E类型的子类型;称之为上限。

如:Vector<? extends Number> x = newvector<Integer>();

2.? super E:可接收E类型或E类型的父类型;称之为下限。

如:Vector<? super Integer>x = newvector<Number>();

示例如下:

/*
泛型的限定:
*/
class GenerticXian2
{
public static void main(String[] args)
{

TreeSet<Student> s = new TreeSet<Student>(new Comp());
s.add(new Student("stu0"));
s.add(new Student("stu3"));
s.add(new Student("stu1"));
print(s);
System.out.println("Hello World!");
TreeSet<Worker> w = new TreeSet<Worker>(new Comp());
w.add(new Worker("Worker0"));
w.add(new Worker("Worker3"));
w.add(new Worker("Worker1"));
print(w);
}

public static void print(TreeSet<? extends Person> ts) {
Iterator<? extends Person> it = ts.iterator();
while (it.hasNext()){
Person p = it.next();
System.out.println(p.getName());
}
}
}

class Person implements Comparable<Person>  {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareTo(Person p){
return this.getName().compareTo(p.getName());
}
}
class Comp implements Comparator<Person> {
public int compare(Person p1,Person p2){
return p1.getName().compareTo(p2.getName());
}
}
class Student extends Person {
Student(String name){
super(name);
}
}

class Worker extends Person {
Worker(String name){
super(name);
}
}


泛型方法

java中泛型方法的定义:

private static <T> T add(T a, T b){

......

return null;

}

add(3,5);//自动装箱和拆箱

Number x1 = add(3.5,5);//取两个数的交集类型Number

Object x2 = add(3,"abc");//去最大交集为Object

1、何时定义泛型方法:为了让不同方法可以操作不同的类型,而且类型不确定,那么就可以定义泛型方法

2、特殊之处:静态方法不可以访问类上定义的泛型,如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。

泛型方法的特点:

1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。

2、只有引用类型才能作为泛型方法的实际参数

3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。

4、普通方法、构造函数和静态方法中都可以使用泛型。

5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。

6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。

public static <K,V> V getValue(K key){

Map<K, V> map = new HashMap<K, V>();

return map.get(key);

}

private static <T extends Exception> void sayHello() throws T{

try{}

catch(Exception e){

throw (T)e;

}

}

这个T和?有什么区别呢?

1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。

2、?为通配符,也可以接收任意类型但是不可以定义变量。

但是这样定义,虽然提高了扩展性,可还是有一个局限性,就是不能使用其他类对象的特有方法。

泛型类

概述:

1、若类实例对象中多出要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。

2、何时定义泛型类:当类中要操作的引用数据类型不确定时,在早期定义Object来完成扩展,而现在定义泛型。

3、泛型类定义的泛型,在整个类中都有效,如果被方法调用,那么泛型类的对象要明确需要操作的具体类型后,所有要操作的类就已经固定了。

4、类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的。

语法格式:

1、定义

public class GenerDao1<T>{

private T field;

public void save(T obj){}

public T getByteId(int Id){}

}

2、举例:

扩展:Dao:Data Access Object,数据访问对象。

对其操作:crud即增上删改查

c:creat,创建、增加; r:read,读取、查询;

u:update,更新、修改 d:delete,删除。

对javaEE的理解:13种技术。简单说就是对数据库的增删改查。

写Dao类有五个基本方法:增删改查,其中查包含查单个和对同类型集合的查询,如同性别或同地区的集合获取。

public class GenerticDao<T> {
public static <E> void staMethod(E e){}
public void add(T obj){}
public boolean delete(T obj){
return true;
}
public boolean delete(int id){
return true;
}
public T update(T obj){
return null;
}
public T findByUserName(String name){
return null;
}
public Set<T> findByPlace(String place){
Set<T> set = new TreeSet<T>();
//....
return set;
}
}


注意:

1、在对泛型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。

2、当一个变量被声明为参数时,只能被实例变量和方法调用(还有内嵌类型),而不能被静态变量和静态方法调用,因为静态成员是被所有参数化的类共享的,所以静态成员不应该有类级别的类型参数。

总结:

对泛型的定义:

第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型

第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类

第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法

代码演示:

//测试
class GenerticTest  {
public static void main(String[] args) {
//创建泛型类对象
GenClass<Worker> g = new GenClass<Worker> ();
g.setTT(new Worker());
Worker w =  g.getTT();
g.showC(w);
System.out.println("----------------------");
//泛型方法测试
GenMethod<String> g1 = new GenMethod<String>();
GenMethod.showS("SSS");
g1.show("sesf");
g1.print("heheh");
g1.printY(new Integer(5));
System.out.println("------------------------");
//泛型接口测试
GenInter g2 = new GenInter();
g2.show("haha");
System.out.println("Hello World!");
GenImpl<Integer> g3 = new GenImpl<Integer>();
g3.show(new Integer(95));
}
}
//泛型类
class GenClass<TT>  {
//定义私有属性
private TT t;
//定义公共设置方法,设置属性
public void setTT(TT t) {
this.t = t;
}
//定义公共访问方法,访问属性
public TT getTT() {
return t;
}
//定义方法
public void showC(TT t) {
System.out.println("GenClass show:" + t);
}
}
//创建Worker类,作为类型传入泛型类中
class Worker {}
//泛型方法
class GenMethod<T> {
//静态的泛型方法
public static <S> void showS(S s) {
System.out.println("static show:" + s);
}
//非静态泛型方法
public void show(T t) {
System.out.println("未指定T show:" + t);
}
public void print(T t) {
System.out.println("指定T print:" + t);
}
//指定接受其他类型的泛型方法
public <Y> void printY(Y y) {
System.out.println("和类指定的不同,为Y print:" + y);
}
}
//泛型接口

interface Inter<T> {
void show(T t);
}
//一般类实现泛型接口
class GenInter implements Inter<String> {
public void show(String s) {
System.out.println("接口 show:" + s);
}
}
//泛型类实现泛型接口
class GenImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("类接收类型不确定的实现接口 show:" + t);
}


参数的类型推断

1、定义:编译器判断泛型方法的实际参数的过程,称之为类型推断。

2、类型推断是相对于直觉推断的,其实现方法是一种非常复杂的过程。

类型推断的具体规则:

根据调用泛型方法时,实际传递的参数类型或返回值的类型来推断。

1、当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时,该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时,传递的参数类型或返回值来决定泛型参数的类型,如:

swap(newString[3],1,2)

---> static <E> void swap(E[] a, inti, int j);

2、当某个类型变量在某个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时,这多处的实际应用类型都对应同一种类型来表示,这很容易凭感觉推断出来:

add(3,5)

---> static<T> T add(T a,T b);

3、若对应了不同类型,且没有使用返回值,这是取多个参数中的最大交集类型,如下面的对应类型Number,编译没问题,但是运行会出错:

fill(new Integer[3],3.5f)

---> static<T> void fill(T[] a,T v);

4、若对应了不同类型,且使用了返回值,这时候优先考虑返回值类型,如下面语句实际对应的类型就是Integer了,编译将报错,将变量x类型改为float,对此eclipse报错提示,接着再将变量x类型改为Number,则就没了错误:

int x = add(3,3.5f)

---> static<T> T add(T a,T b);

5、参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没问题,而第二种情况则会根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:

copy(newInteger[5],new String[5]);

---> static<T> T copy(T[] a,T[] b);

类加载器

1、定义:简单说,类加载器就是加载类的工具。

当出现一个类,用到此类的时候,Java虚拟机首先将类字节码加载进内存,通常字节码的原始信息放在硬盘上的classpath指定的目录下。

2、类加载器作用:将.class文件中的内容加载进内存进行处理,处理完后的结果就是字节码。

3、默认类加载器:

1)Java虚拟机中可安装多个类加载器,系统默认的有三个主要的,每个类负责加载特定位置的类:BootStrap、ExtClassLoader、AppClassLoader

2)BootStrap--顶级类加载器:

类加载器本身也是Java类,因为它是Java类,本身也需要加载器加载,显然必须有第一个类加载器而不是java类的,这正是BootStrap。它是嵌套在Java虚拟机内核中的,已启动即出现在虚拟机中,是用c++写的一段二进制代码。所以不能通过java程序获取其名字,获得的只能是null。

4、Java虚拟机中的所有类加载器采用子父关系的树形结构进行组织,在实例化每个类加载器对象或默认采用系统类加载器作为其父级类加载器。

package cn.itcast.text2;
import java.util.Date;
public class ClassLoadTest{
public static void main(String[] args) throws Exception{
System.out.println(
ClassLoadTest.class.getClassLoader().
getClass().getName());//为AppClassLoader
System.out.println(
System.class.getClassLoader());//为null
}
}


类加载器的委托机制:

1、加载类的方式

当Java虚拟机要加载一个类时,到底要用哪个类加载器加载呢?

1)首先,当前线程的类加载器去加载线程中的第一个类。

2)若A引用类B(继承或者使用了B),Java虚拟机将使用加载类的类加载器来加载类B。

3)还可直接调用ClassLoader的LoaderClass()方法,来制定某个类加载器去加载某个类。

2、加载器的委托机制:每个类加载器加载类时,又先委托给上级类加载器。

每个ClassLoader本身只能分别加载特定位置和目录中的类,但他们可以委托其他类的加载器去加载,这就是类加载器的委托模式,类加载器一级级委托到BootStrap类加载器,当BootStrap在指定目录中没有找到要加载的类时,无法加载当前所要加载的类,就会一级级返回子孙类加载器,进行真正的加载,每级都会先到自己相应指定的目录中去找,有没有当前的类;直到退回到最初的类装载器的发起者时,如果它自身还未找到,未完成类的加载,那就报告ClassNoFoundException的异常。

简单说,就是先由发起者将类一级级委托为BootStrap,从父级开始找,找到了直接返回,没找到再返回给其子级找,直到发起者,再没找到就报异常。

3、委托机制的优点:可以集中管理,不会产生多字节码重复的现象。

面试题

可不可以自己写个类为:java.lang.System呢?

回答:第一、通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System。

第二、但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。

自定义类加载器

1、自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(String name)方法,而不用覆写loadClass()方法。

2、覆写findClass(String name)方法的原因:

1)是要保留loadClass()方法中的流程,因为loadClass()中调用了findClass(String name)这个方法,此方法返回的就是去寻找父级的类加载器。

2)在loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(String name)方法,而找不到时就会用子级的类加载器,再找不到就报异常了,所以只需要覆写findClass方法,那么就具有了实现用自定义的类加载器加载类的目的。

流程:

父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defind()。

3、编程步骤:

1)编写一个对文件内容进行简单加盟的程序

2)编写好了一个自己的类加载器,可实现对加密过来的类进行装载和解密。

3)编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可使用ClassLoader的load方法外,还能使用放置线程的上线文类加载器加载或系统类加载器,然后在使用forName得到字节码文件。

代码演示:

package cn.itcast.text2;
import java.util.Date;

public class ClassLoaderAttachment extends Date {
//对此类进行加密
public String toString(){
return "hello world";
}
public static void main(String [] args){

}
}

//自定义类加载器
package cn.itcast.text2;

import java.io.*;
//继承抽象类ClassLoader
public class MyClassLoader  extends ClassLoader {
public static void main(String[] args) throws Exception {
//传入两个参数,源和目标
String scrPath = args[0];
String destDir = args[1];
//将数据读取到输入流中,并写入到输出流中
FileInputStream fis = new FileInputStream(scrPath);
String destFileName =
scrPath.substring(scrPath.lastIndexOf('\\')+1);
String destPath = destDir + "\\" + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
//加密数据
cypher(fis,fos);
fis.close();
fos.close();
}
//定义加密数据的方法
private static void cypher(InputStream ips,OutputStream ops)throws Exception{
int b = 0;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);
}
}
//定义全局变量
private String classDir;
@Override//覆写findClass方法,自定义类加载器
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
//将要加载的文件读取到流中,并写入字节流中
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//如果没找到类,则用父级类加载器加载
return super.findClass(name);
}
//构造函数
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}


----------------------
ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: