黑马程序员_枚举 泛型 类加载器

时间:2023-02-17 18:20:20

------- android培训java培训、期待与您交流! ----------

 

一.枚举

一.概述

   1.枚举是jdk1.5的一个新特性。

   2.枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个。否则编译报错。

   3.枚举可以让编译器在编译时就可以控制源程序中填写的非法值。普通变量的方式在开发阶段无法达到这一目标。

   4.如果想打印枚举元素的信息,需要在此类中自定义toString()方法。

二.用普通的类模拟实现枚举

  代码:

 package cn.itheima.day1;
/*用普通的类模拟实现枚举*/
public abstract class WeekDay1 {
private WeekDay1() {
}
public static WeekDay1 SUN = new WeekDay1() {
@Override
public WeekDay1 nextDay() {
return MON;
}
}; // SUN是常量
public static WeekDay1 MON = new WeekDay1() {
@Override
public WeekDay1 nextDay() {
return SUN;
}
};
// 抽象方法
public abstract WeekDay1 nextDay();
public String toString() {
return this == SUN ? "SUN" : "MON";
}
}

三.枚举的基本应用

   1.枚举就相当于一个类,其中也可以定义构造方法,成员变量,普通方法和抽象方法。

   2.枚举元素必须位于枚举体中的最开始部分,枚举元素后要有分号与其它成员分开,若把枚举中的成员方法或变量放在枚举元素之前,编译器会报

错。

   3.枚举只有一个成员时,就可以作为一种单例的实现方式。

   4.每一个枚举元素就是一个对象。

5.用enum关键字定义枚举类。

注意:

枚举类是class,而且是一个不可被继承的final类,其中的元素都是类静态常量。

枚举中常用的几个方法(继承Enum类中的方法):

     String toString() ;// 返回枚举常量的名称,它包含在声明中。

String name();//返回此枚举常量的名称,在其枚举声明中对其进行声明。

     int ordinal() ;// 返回枚举常量的序数。       

Class getClass() ;//获取对应的类名。

   静态方法:

          valueOf(String str) ;//将字符串转为枚举对象。

           values() ;//获取所有的枚举对象,返回的是一个数组。

   

四.实现带有构造方法的枚举

1.构造方法必须是私有的,枚举值是public static final的常量,但是枚举类的方法和数据域是可以被外部访问的。

2.在对象后加()和默认的是一样的,都访问的是无参数的构造函数。

3.如果对象后的()内有参数,那么则访问的是有参数的构造函数。

五.实现带有抽象方法的枚举

   1.实现抽象的方法时,每个元素分别是由枚举类的子类来生成的示例对象,这些子类用类似内部类的方式定义。

  代码:

package cn.itheima.day1;
public class EnumTest {
public static void main(String[] args) {
WeekDay weekday2 = WeekDay.FRI;
System.out.println(weekday2);// 自动实现toString方法
System.out.println(weekday2.name()); //返回此枚举常量的名称,在其枚举声明中对其进行声明。
System.out.println(weekday2.ordinal()); // 返回枚举常量的序数。
System.out.println(WeekDay.valueOf("SUN").toString()); // 将字符串变成了枚举对象
System.out.println(WeekDay.values().length); // 获取所有的枚举对象,返回的是一个数组。打印长度

}
public enum WeekDay {
SUN(1), MON(), TUE(), WED, THI, FRI, SAT;//分号可以省略,如果后边有语句,必须要有

// 实现带有构造方法的枚举
// 构造方法必须是私有的
// 在对象后加()和默认的是一样的,都访问的是无参数的构造函数
// 如果对象后的()内有参数,那么则访问的是有参数的构造函数

// 总结: 在元素后加(),就表示创建这个元素实例对象的时候,使用那个构造方法

// 无参数的构造函数
private WeekDay() {
System.out.println("first");
}

// 有参数的构造函数
private WeekDay(int day) {
System.out.println("second");
}

// 实现带有抽象方法的枚举
public enum TrafficLamp {

RED(30) {
/*
* 创建一个子类的实例对象,并调用父类有参数的构造函数,用RED这个引用名称去引用该对象。
* */
public TrafficLamp nextLamp() {
return GREEN;
}
},
GREEN(45) {
public TrafficLamp nextLamp() {
return YELLOW;
}
},
YELLOW(5) {
public TrafficLamp nextLamp() {
return RED;
}
};
// 抽象方法
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time) {
this.time = time;
}
}
}
}

 

二.泛型

一.概述

1.泛型也是jdk1.5的一个新特性。用于解决安全问题,是一个类型安全机制。     

2.定义格式:通过<>来定义要操作的引用数据类型。

3.泛型的作用:

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉

“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉

泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据。

例如:用反射得到集合,再调用其add方法。

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

    collection.getClass().getMethod(“add”,Object.class).invoke(collection,”abc”);

    System.out.println(collection.get(0));//abc

   

4.泛型的好处:

a.将运行时期出现的问题classCastException,转移到了编译时期,方便于程序员解决问题,让运行时的问题减少,这样更安全。

   b.避免了强制转换。

 5.泛型中的术语解释

以ArrayList<E>类定义和ArrayList<Integer>类引用为例:

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

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

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

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

ArrayList<Integer>中的<>读typeof

ArrayList称为原始类型

6.参数化类型

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

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

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

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

例如:Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去

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

Vector<String> v = new Vector<Object>(); //错误!///不写<Object>

Vector<Object> v = new Vector<String>(); //错误!

编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。

例如:Vector<Integer> vectorList[] = new Vector<Integer>[10];

 

二.通配符 ?

 1.通配符的使用

定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义?

错误方式:

public static void printCollection(Collection<Object> cols) {
for(Object obj:cols) {
System.out.println(obj);
}
/* cols.add("string");//没错
cols = new HashSet<Date>();//会报告错误!*/
}

正确方式:

public static void printCollection(Collection<?> cols) {
for(Object obj:cols) {
System.out.println(obj);
}
//cols.add("string");//错误,因为它不知自己未来匹配就一定是String
cols.size();//没错,此方法与类型参数没有关系
cols = new HashSet<Date>();
}

总结:

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

 2.泛型限定

限定通配符的上边界:

正确:Vector<? extends Number> x = new Vector<Integer>();

错误:Vector<? extends Number> x = new Vector<String>();

限定通配符的下边界:

正确:Vector<? super Integer> x = new Vector<Number>();

错误:Vector<? super Integer> x = new Vector<Byte>();

   注意:限定通配符总是包括自己。

?只能用作引用,不能用它去给其他变量赋值。

三.自定义泛型

  Java中的泛型类型(或者泛型)类似于 C++中的模板。但是这种相似性仅限于表面,Java语言中的泛型基本上完全是在编译器中实现,用于编

译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,

然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的

障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。   

1.定义泛型方法

交换数组中的两个元素的位置的泛型方法语法定义如下:

static <E> void swap(E[] a, int i, int j) {
E t = a[i];
a[i] = a[j];
a[j] = t;
}


 

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

2.extends限定符

除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符,例如,Class.getAnnotation()方法的定义。并且可

以用&来指定多个边界,如<V extends Serializable & cloneable> void method(){}

3.泛型的使用

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

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

在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分隔,例如:

public static <K,V> V getValue(K key) { return map.get(key);}

注意:

     用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数

通常用单个大写字母表示。

//练习1:编写一个泛型方法,自动将Object类型的对象转换为其它类型。
private static <T> T autoConvert(Object obj){
return(T)obj;
}
调用:Object obj=”abc”;
String x3=autoConvert(obj);
//练习2:定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
private static <T> fillArray(T[] a,T obj){
for(int i=0;i<a.length;i++){
a[i]=obj; }
}


 

四.类型参数的类型推断

1.编译器判断泛型方法的实际类型参数的过程称为类型判断。

2.根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:

a.当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着

感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:

     swap(new String[3],3,4)   static <E> void swap(E[] a, int i, int j)

b.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,

这很容易凭着感觉推断出来,例如:

          add(3,5) static <T> T add(T a, T b)

c.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没

有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:

        fill(new Integer[3],3.5f)  à static <T> void fill(T[] a, T v)

d.当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,并且

使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,

对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:

           int x =(3,3.5f)  à static <T> T add(T a, T b)

e.参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例

将类型变量直接确定为String类型,编译将出现问题:

      copy(new Integer[5],new String[5])à static <T> void copy(T[] a,T[] b);

 copy(new Vector<String>(), new Integer[5])à static <T> void

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

 

五.定义泛型类型

  如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类要保持同一个实际类型时,这时候要采用泛型类型的方式进行定

义,也就是类级别的泛型。

 1.格式:

         public  class GenericDao<T>{

             private  T field;

             public void save(T obj){}

             public  T  getById(int  id){}

}

类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

GenericDao<String> dao = null;

new genericDao<String>();

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

当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所

共享的,所以静态成员不应该有类级别的类型参数。

当类中只有一个方法需要使用泛型,要使用类级别的泛型。

代码:

package cn.itheima.day2;
import java.util.Set;
public class GenericDao<T> {
public void add(T x) {
}
public T findById(int id) {
return null;
}
public void delete(T obj) {
}
public void delete(int id) {
}
public void update(T obj) {
}
public T findByUserName() {
return null;
}
public Set<T> findByConditons(String where) {
return null;
}
}

  

三.类加载器

一.概述

  1.定义:类加载器就是加载类的工具。

2.作用:将class文件加载到内存中。

3.系统默认的类加载器

    java系统中可以安装多个类加载器,但是系统默认的主要类加载器有三个:

      BootStrap   ExtClassLoader   AppClassLoader

   类加载器也是java类,因为其他的也是java类的类加载器本身也要被类加载器加载,因而必须有一个类加载器不是java类,那就是ootStrap。

它是嵌套在Java虚拟机内核中的,是用c++写的一段二进制代码。所以不能通过java程序获取其name,返回为null。     

代码:

  package cn.itheima.day2;

public class ClassLoaderDemo1 {
public static void main(String[] args) {
System.out.println(ClassLoaderDemo1.class.getClassLoader().getClass()
.getName());// AppClassLoader

System.out.println(System.class.getClassLoader());// null -->说明System类是由BootStrap类加载加载的

// 类加载器的层次结构关系
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while (loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}

System.out.println(loader);
}
/* 输出结果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
null */
}

二.类加载器的委托机制

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。

 1.Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者

默认采用系统类装载器为其父级类加载。

   类加载器之间的父子关系和管辖范围图

黑马程序员_枚举  泛型  类加载器

问题:当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

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

如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

 

2.委托

每个类加载器加载类时,又先委托给其上级类加载器。

当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的下一

级。

 3.委托机制的好处:

      可以集中管理,避免内存中出现相同的多个字节码。

 4.扩展问题

能不能自己写一个java.lang.System类呢?

  答案:通常不可以,因为类加载器的委托机制,会将该类的加载委托给BootStrap, 而Bootstrap类加载器在tr.jar中找到了该类,不会去找自

己写的System类;如果指定用自定义的类加载器去加载自己写的System类,这样是可行的。

 

三.自定义类加载器

   1.步骤:

a.自定义的类加载器的必须继承ClassLoader

      b.覆写findClass()方法。

      c.调用defineClass()方法。该方法将一个字节数组转换为Class类的实例。

  2.分析:

      为什么只覆写findClass()方法?

      因为loadClass()方法内部是会先委托给父级,当父级找不到返回后,再调用findClass()方法,所以只需要覆写findClass方法,就能

实现用自定义的类加载器加载类的目的。

  3.在父类的loadClass()方法中保留了流程(类加载器的委托),在复写的findClass()方法中实现具体的细节,这就是模板方法设计模式。

  4.代码体现:

编写一个对文件内容进行简单加密的程序。

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

编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用

ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。

  

5.实验步骤:

对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如: java MyClassLoader MyTest.class F:\itcast

运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast

用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

代码:

package com.itheima.classLoader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
//自定义的类加载器
public class MyClassLoader extends ClassLoader
{

public static void main(String[] args) throws Exception {
//源路径
String srcPath=args[0];
//目的路径
String destDir=args[1];
//创建流
FileInputStream fis=new FileInputStream(srcPath);
//获取目的文件名称
String destFileName=srcPath.substring(srcPath.lastIndexOf('\\')+1);
//文件存储的目的地,包括文件名
String destPath=destDir+"\\"+destFileName;
FileOutputStream fos=new FileOutputStream(destPath);
System.out.println(destPath);
//加密操作
cypher(fis, fos);
//关闭流
fis.close();
fos.close();

}
//加密/解密方法
private static void cypher(InputStream ips,OutputStream ops) throws IOException{
int b=-1;
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) {
e.printStackTrace();
}
return null;
}
//构造方法
public MyClassLoader(String classDir){
this.classDir=classDir;
}
public MyClassLoader(){
}
}
package com.itheima.classLoader;
import java.util.Date;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
// 用自定义的类加载器加载
Class cl= new MyClassLoader("itheimalib").loadClass("ClassLoaderAttachment");
Date d1=(Date)cl.newInstance();
System.out.println(d1);
}
}
//测试类代码:
package com.itheima.classLoader;
import java.util.Date;
public class ClassLoaderAttachment extends Date {
public String toString()
{
return "黑马程序员";
}
}


 






 

 

------- android培训java培训、期待与您交流! ----------