------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
黑马程序员Java基础加强
1. 泛型是JDK1.5中一个最重要的特征。所谓泛型:就是变量类型的参数化。泛型之前,如果类别定义时的逻辑完全一样,只是里面成员变量的类型不同,需要定义多个类,这使类的数量急剧膨胀。针对这种情况,在设计了泛型。在定义类时,里面使用的类型不确定,而是让用户指定,这样减少了大量的类的定义。在定义泛型类别时,预设可以使用任何的类型来实例化泛型类型中的类型,但是如果想要限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口。当没有指定泛型继承的类型或接口时,默认使用T extends Object,所有默认情况下任何类型都可以作为参数传入。现在有这么一个需求,希望有一个参考名称foo可以接受所有下面的实例:
foo = new GenericFoo<ArrayList>();
foo = new GenericFoo<LinkedList>();
简单来说,实例化类型持有者时,它必须是实现List的类别或其子类别。这时,就要用’?’通配符,并使用”extends”关键字限定类型持有者。我们就可以这样写:
GenericFoo<? Extends List> foo = null;这样就可以让foo指向上面的类型了。<?>的意思也是<? extends Object>。<T>在定义类使用,而<?>是创建对象时使用。
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
如果使用泛型,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException。同时,避免了强制转换麻烦。
什么时候定义泛型类?当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展。现在定义泛型来完成扩展。
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
方法中使用泛型
private static <T extends Exception> sayHello() throws T
{
try{
}catch(Exception e){
throw (T)e;
}
}
类定义中使用泛型:
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
publicclass GenericTest<T>
{
private T foo;
public T getFoo()
{
returnfoo;
}
publicvoid setFoo(T foo)
{
this.foo = foo;
}
publicstaticvoid main(String[] args)
{
GenericTest<? extends List> ge = null;
ge = new GenericTest<ArrayList>();
ge = new GenericTest<LinkedList>();
GenericTest<? super List> ge2 = null;
ge2 = new GenericTest<Object>();
GenericTest<String> ge3 = new GenericTest<String>();
ge3.setFoo("hello world");
GenericTest<?> ge4 = ge3;
System.out.println(ge4.getFoo());
ge4.setFoo(null);
System.out.println(ge4.getFoo());
}
}
2. For-Each循环的加入简化了集合的遍历,语法形式是:for(type element : array) {
System.out.println(element)……
}
当遍历集合或数组时,如果需要访问集合或数组的下标,那么最好使用旧式的方式来实现循环或遍历,而不要使用增强的for循环,因为它丢失了下标信息。
3. 自动装箱/拆箱方便了基本类型数据和它们包装类的使用。自动装箱:基本类型自动转为包装类(如int>>Integer)。自动拆箱:包装类自动转为基本类型(如Integer>>int)。
import java.util.ArrayList;
import java.util.Collection;
public class BoxTest
{
public static void main(String[] args)
{
int a = 3;
Collection<Integer> c = new ArrayList<Integer>();
c.add(3);//将int类型的3转换为Integer类型并放到集合当中
c.add(a + 3);
for(Integer i : c)
{
System.out.println(i);
}
}
}
Integer类有个缓存,它会缓存介于-128到127之间的整数。这是享元模式的一个实例。
Integer i1 = 200;
Integer i2 = 200;
if(i1 == i2)
{
System.out.println("i1 == i2");
}
else
{
System.out.println("i1 != i2");
}
Integer i3 = 20;
Integer i4 = 20;
if(i3 == i4)
{
System.out.println("i3 == i4");
}
else
{
System.out.println("i3 != i4");
}
4. 可变参数使我们可以声明一个接受可变数目参数的方法。它本质上就是一个数组,对于某个声明了可变参数的方法来说,我们既可以传递离散的值,也可以传递数组对象。但如果将方法中的参数定义为数组,那么只能传递数组对象而不能传递离散的值。可变参数必须作为方法参数的最后一个参数,即一个方法不可能具有两个或两个以上的可变参数。
public class TestVarargs
{
private static int sum(String str, int... nums)
{
System.out.println(str);
int sum = 0;
for (int num : nums)
{
sum += num;
}
return sum;
}
public static void main(String[] args)
{
int result = sum("a", new int[] { 1, 2 });
System.out.println(result);
result = sum("b", 1, 2, 3, 4);
System.out.println(result);
}
}
5. 静态导入可以导入一个类中的某个静态方法或所有静态方法,使用这些静态成员无需再给出它们的类名。例如import static java.lang.Math.*;
6. 枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法,只不过很多细节由编译器帮你完成了,所以某些程度上,enum关键字的作用就像是class或interface。定义枚举类型时,它将继承自java.lang.Enum类型。每个枚举的成员其实就是你定义的枚举类型的一个实例,它们都是public static final的。这些实例在编译期间就确定下来了,在运行期间我们无法再使用该枚举类型创建新的实例了。
枚举只有一个成员时,就可以作为一种单例的实现方式。
枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
public enum Coin
{
penny("hello"), nickel("world"), dime("welcome"), quarter("hello world");
private String value;
public String getValue()
{
return value;
}
Coin(String value)
{
this.value = value;
}
public static void main(String[] args)
{
Coin coin = Coin.quarter;
System.out.println(coin.getValue());
}
}
7. 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
Override注解表示子类要重写父类的对应方法。Deprecated注解表示方法是不建议被使用的。SuppressWarnings注解表示抑制警告。
自定义注解的方法。定义一个最简单的注解:public @interface MyAnnotation {}。把它加在某个类上:@MyAnnotation public class AnnotationTest{}。用反射进行测试AnnotationTest的定义上是否有@MyAnnotation 。根据发射测试的问题,引出@Retention元注解的讲解,其三种取值:RetetionPolicy.SOURCE、RetetionPolicy.CLASS、RetetionPolicy.RUNTIME;分别对应:java源文件-->class文件-->内存中的字节码。
当注解中的属性名为value时,在对其赋值时可以不指定属性的名称而直接写上属性值即可;除了value以外的其他值都需要使用name=value这种赋值方式,即明确指定给谁赋值。
定义基本类型的属性和应用属性:在注解类中增加String color();@MyAnnotation(color="red")
用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法
Ø MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
Ø System.out.println(a.color());
Ø 可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象
l 为属性指定缺省值:
Ø String color() default "yellow";
l value属性:
Ø String value() default "zxx";
Ø 如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
l 数组类型的属性
Ø int [] arrayAttr() default {1,2,3};
Ø @MyAnnotation(arrayAttr={2,3,4})
Ø 如果数组属性中只有一个元素,这时候属性值部分可以省略大括
l 枚举类型的属性
Ø EnumTest.TrafficLamp lamp() ;
Ø @MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
l 注解类型的属性:
Ø MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
Ø @MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )
Ø 可以认为上面这个@MyAnnotation是MyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotation是MetaAnnotation类的一个实例对象,调用代码如下:
MetaAnnotation ma = myAnnotation.annotationAttr();
System.out.println(ma.value());
还可以是class类型
public @interface AnnotationTest
{
String[] value1() default "hello";//给它赋默认值
EnumTest value2();
}
enum EnumTest
{
Hello, World, Welcome;
}
@AnnotationTest(value2 = EnumTest.Welcome)
public class AnnotationUsage
{
@AnnotationTest(value1 = {"world", "ABCD"}, value2 = EnumTest.World)
public void method()
{
System.out.println("usage of annotation");
}
public static void main(String[] args)
{
AnnotationUsage usage = new AnnotationUsage();
usage.method();
}
}
8. 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类加载的最终产品是位于堆区中的Class对象。有两种类型的类加载器:1.java虚拟机自带的加载器:根类加载器,扩展类加载器,系统类加载器。其中根类加载器不是java类,其他都是java类。2.用户自定义的类加载器
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
Java采用父委托机制进行加载,各个加载器按照父子关系形成了树形结构,除了根类加载器外,其余的都有且只有一个父加载器。父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。当生成一个自定义的类加载器实例时,如果没有指定它的父加载器,那么系统类加载器就将成为该类加载器的父加载器。
l 当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
Ø 首先当前线程的类加载器去加载线程中的第一个类。
Ø 如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
Ø 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
l 每个类加载器加载类时,又先委托给其上级类加载器。
Ø 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
l 知识讲解:
Ø 自定义的类加载器的必须继承ClassLoader
Ø loadClass方法与findClass方法
Ø defineClass方法
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader
{
private String name; //类加载器的名字
private String path = "d:\\"; // 加载类的路径
private final String fileType = ".class"; // class文件的扩展名
public MyClassLoader(String name)
{
super(); // 让系统类加载器成为该类加载器的父加载器
this.name = name;
}
public MyClassLoader(ClassLoader parent, String name)
{
super(parent); // 显式指定该类加载器的父加载器
this.name = name;
}
@Override
public String toString()
{
return this.name;
}
public String getPath()
{
return path;
}
public void setPath(String path)
{
this.path = path;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name)
{
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try
{
this.name = this.name.replace(".", "\\");
is = new FileInputStream(new File(path + name + fileType));
baos = new ByteArrayOutputStream();
int ch = 0;
while(-1 != (ch = is.read()))
{
baos.write(ch);
}
data = baos.toByteArray();
}
catch(Exception ex)
{
ex.printStackTrace();
}
finally
{
try
{
is.close();
baos.close();
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception
{
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("d:\\myapp\\serverlib");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("d:\\myapp\\clientlib");
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setPath("d:\\myapp\\otherlib");
test(loader2);
test(loader3);
}
public static void test(ClassLoader loader) throws Exception
{
Class clazz = loader.loadClass("Sample");
Object object = clazz.newInstance();
}
}