java基础2

1)枚举

以枚举定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。

在编译时自动继承enum接口,并且枚举变量都被隐式的声明为static final在反编译中可以看出。

枚举也有.class文件也是个类。而且在编译期间自动加入了很多static方法:

编译器为我们添加的静态的values()方法:获得所有枚举类

编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf(String s)方法:返回字符串对应的枚举类型。

并且添加了私有的构造函数: private Day(String s, int i) { super(s, i); }-》s对应枚举类型,i代表声明的下标从0开始

并且编译期间已经使用static方法为我们构建好了:

    static 
    {    
        //实例化枚举实例
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }

而且这个枚举类是final类型的,即不能被继承。

当枚举实例向上转型为Enum类型后,values()方法将会失效,也就无法一次性获取所有枚举实例变量,但是由于Class对象的存在,即使不使用values()方法,还是有可能一次获取到所有枚举实例变量的:getEnumConstants():返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。isEnum():当且仅当该类声明为源代码中的枚举时返回 true。

enum类可以添加方法与自定义构造函数(而且是私有的private的)

在覆盖父类的方法方面:

父类Enum中的定义的方法只有toString方法没有使用final修饰,因此只能覆盖toString方法。

enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式

public enum Enum1 {

    FIRST{
        @Override
        public String getInfo() {
            return "FIRST TIME";
        }
    },


    /**
     * 定义抽象方法
     * @return
     */
    public abstract String getInfo();
}

switch支持枚举类型

使用switch进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被switch所支持。

不能使用反射创建枚举类

而且已经实现了序列化接口,并且序列化得到的还是同一个对象。

枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性。

枚举的单例实现

public class User {
    //私有化构造函数
    private User(){ }
 
    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数
        private SingletonEnum(){
            user=new User();
        }
        public User getInstnce(){
            return user;
        }
    }
 
    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

2)反射

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

1.获取类对象的方法

》知道具体类的对象的时候:类名.class

》类对象的getClass()方法

》通过 Class.forName()传入类的路径获取

2.具体操作举例

         /**
         * 获取TargetObject类的Class对象并且创建TargetObject类实例
         */
        Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
        TargetObject targetObject = (TargetObject) tagetClass.newInstance();
        /**
         * 获取所有类中所有定义的方法
         */
        Method[] methods = tagetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        /**
         * 获取指定方法并调用
         */
        Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
                String.class);

        publicMethod.invoke(targetObject, "JavaGuide");
        /**
         * 获取指定参数并对参数进行修改
         */
        Field field = tagetClass.getDeclaredField("value");
        //为了对类中的参数进行修改我们取消安全检查
        field.setAccessible(true);
        field.set(targetObject, "JavaGuide");
        /**
         * 调用 private 方法
         */
        Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
        //为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(targetObject);

静态编译和动态编译

  • 静态编译: 在编译时确定类型,绑定对象
  • 动态编译: 运行时确定类型,绑定对象

反射机制优缺点

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
  • 缺点: 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。

应用:

动态代理

使用反射可以在运行时创建接口的动态实现,java.lang.reflect.Proxy类提供了创建动态实现的功能。我们把运行时创建接口的动态实现称为动态代理。

动态代理可以用于许多不同的目的,例如数据库连接和事务管理、用于单元测试的动态模拟对象以及其他类似aop的方法拦截等。

创建代理

调用java.lang.reflect.Proxy类的newProxyInstance()方法就可以常见动态代理,newProxyInstance()方法有三个参数: 1、用于“加载”动态代理类的类加载器。
2、要实现的接口数组。 3、将代理上的所有方法调用转发到InvocationHandler的对象。 代码如下:

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
                            MyInterface.class.getClassLoader(),
                            new Class[] { MyInterface.class },
                            handler);

运行上面代码后,proxy变量包含了MyInterface接口的动态实现。

对代理的所有调用都将由到实现了InvocationHandler接口的handler 对象来处理。

InvocationHandler
如上面说的一样,必须将InvocationHandler的实现传递给Proxy.newProxyInstance()方法。对动态代理的所有方法调用都转发到实现接口的InvocationHandler对象。 InvocationHandler代码:

public interface InvocationHandler{
  Object invoke(Object proxy, Method method, Object[] args)
         throws Throwable;
}

实现InvocationHandler接口的类:

public class MyInvocationHandler implements InvocationHandler{

  public Object invoke(Object proxy, Method method, Object[] args)
  throws Throwable {
    //do something "dynamic"
  }
}

下面详细介绍传递给invoke方法的三个参数。

Object proxy参数,实现接口的动态代理对象。通常不需要这个对象。

Method method参数,表示在动态代理实现的接口上调用的方法。通过Method对象,可以获取到方法名,参数类型,返回类型等信息。

Object[] args参数,包含调用接口中实现的方法时传递给代理的参数值。注意:如果接口中的参数是int、long等基本数据时,这里的args必须使用Integer, Long等包装类型。

上面代码中会生成一个MyInterface接口的对象proxy,通过proxy对象调用的方法都会由MyInvocationHandler类的invoke方法处理。

动态代理使用场景:
1、数据库连接和事务管理。例如Spring框架有一个事务代理,可以启动和提交/回滚事务 
2、用于单元测试的动态模拟对象 
3、类似AOP的方法拦截。

反射是框架设计的灵魂

使用的前提条件:必须先得到代表的字节码的Class,Class类用于表示.class文件(字节码)

通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的

例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?则需要使用反射

3)泛型

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.

泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型

参数化类型:

  • 把类型当作是参数一样传递

  • <数据类型> 只能是引用类型

1.为什么要用泛型

泛型(又称类型检验):这个是发生在编译期的。编译器负责检查程序中类型的正确性,然后把使用了泛型的代码翻译或者重写成可以执行在当前JVM上的非泛型代码。这个技术被称为“类型擦除“。

泛型类或者泛型方法中,不接受 8 种基本数据类型。

早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全

首先,我们来试想一下:没有泛型,集合会怎么样

  • Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。

  • 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换

有了泛型以后:

  • 代码更加简洁【不用强制转换】

  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】

  • 可读性和稳定性【在编写集合的时候,就限定了类型】

2.编译时期和运行时期

编译时期:检查是否有语法错误,如果没有就将其翻译为字节码文件,.class

运行时期:java虚拟机分配内存,解释执行字节码文件。

java编译时期会做一些优化操作。

1、方法重载 在编译时执行;方法重写 在运行时执行。

2、泛型(类型检测),在编译时。

3、注解,有的在编译时,有的在运行时。@Override注解就是典型的编译时注解,他会在编译时会检查一些简单的如拼写的错误(与父类方法不相同)等
同样的@Test注解是junit框架的注解,他是一个运行时注解,他可以在运行时动态的配置相关信息如timeout等。

4、AOP 可以在编译时,预编译时以及运行时使用。编译时:当你有源码的时候,AOP编译器(AspectJ编译器)可以编译源码并且生成编译后的class;预编译时:织入过程有时候也叫作二进制织入,用来织入到 已经存在的class文件;运行时:当被织入的对象已经被加载到JVM中后,可以动态的织入到这些类中的一些信息。

5、继承是在编译时期运行的

6、代理,也成为动态代理,在运行时期执行。

以上泛型内容出自http://www.360doc.com/content/17/0316/18/18550343_637424471.shtml

以下泛型内容出自https://blog.youkuaiyun.com/qq_36761831/article/details/82289786?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param

3.泛型类

泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来….这样的话,用户明确了什么类型,该类就代表着什么类型…用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。

但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如: 
1、T 代表一般的任何类。 
2、 E 代表 Element 的意思,或者 Exception 异常的意思。 
3、K 代表 Key 的意思。 
4、 V 代表 Value 的意思,通常与 K 一起配合使用。 
5、S 代表 Subtype 的意思

4.泛型方法

public class Test1 {
 
   public <T> void testMethod(T t){
 
   }
}

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。<T> 中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。声明的类型参数,也是可以当作返回值的类型的。

public  <T> T testMethod1(T t){
       return null;
}

泛型类与泛型方法的共存现象

public class Test1<T>{
 
   public  void testMethod(T t){
       System.out.println(t.getClass().getName());
   }
   public  <T> T testMethod1(T t){
       return t;
   }
}

上面代码中,Test1<T> 是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准。针对上面的代码,可以这样编写测试代码:

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));

泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是 Integer,两者不想干。

但是,为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,Test1<T> 代码可以更改为这样

public class Test1<T>{
 
   public  void testMethod(T t){
       System.out.println(t.getClass().getName());
   }
   public  <E> E testMethod1(E e){
       return e;
   }
}

5.泛型接口

泛型接口和泛型类差不多

6.通配符 ?

除了用 <T> 表示泛型外,还有 <?> 这种形式。? 被称为通配符。

通配符有 3 种形式。

  1. <?> 被称作无限定的通配符。

  2. <? extends T> 被称作有上限的通配符。

  3. <? super T> 被称作有下限的通配符。

无限定通配符<?>

public void testWildCards(Collection<?> collection){
}

上面的代码中,方法内的参数是被无限定通配符修饰的 Collection 对象,它隐略地表达了一个意图或者可以说是限定,那就是 testWidlCards() 这个方法内部无需关注 Collection 中的真实类型,因为它是未知的。所以,只能调用 Collection 中与类型无关的方法。

<?> 提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空?我想这种需求还是很常见的吧。

<? extends T>

<?> 代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。

但是,它仍然丧失了写操作的能力

<? super T>

这个和 <? extends T> 相对应,代表 T 及 T 的超类。

<? super T> 有一定程度的写操作的能力。

通配符与类型参数的区别

一般而言,通配符能干的事情都可以用类型参数替换。 比如

public void testWildCards(Collection<?> collection){}

可以被

public <T> void test(Collection<T> collection){}

取代。

注意:如果用泛型方法来取代通配符,那么上面代码中 collection 是能够进行写操作的。只不过要进行强制转换。

public <T> void test(Collection<T> collection){
   collection.add((T)new Integer(12));
   collection.add((T)"123");
}

特别注意:类型参数适用于参数之间的类别依赖关系:

public class Test2 <T,E extends T>{
   T value1;
   E value2;
}
public <D,S extends D> void test(D d,S s){
 
   }

E 类型是 T 类型的子类,显然这种情况类型参数更适合。 
有一种情况是,通配符和类型参数一起使用。

public <T> void test(T t,Collection<? extends T> collection){
 
}

如果一个方法的返回类型依赖于参数的类型,那么通配符也无能为力。

public T test1(T t){
   return value1;
}

4)注解

1.@Retention

@Retention只能用于修饰其他的Annotation,用于指定被修饰的Annotation被保留多长时间。

@Retention 包含了一个RetentionPolicy类型的value变量,所以在使用它的时候,必须要为value成员变量赋值

RetentionPolicy使用的是枚举只有三个值

2.@Target

只能用于修饰另外的Annotation它用于指定被修饰的Annotation用于修饰哪些程序单元

public enum ElementType {
    /**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,

    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,

    /** 标明该注解可以用于方法声明 */
    METHOD,

    /** 标明该注解可以用于参数声明 */
    PARAMETER,

    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,

    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,

    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,

    /** 标明注解可以用于包声明 */
    PACKAGE,

    /**
     * 标明注解可以用于类型参数声明(1.8新加入)
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * 类型使用声明(1.8新加入)
     * @since 1.8
     */
    TYPE_USE
}

3.@Documented

用于指定被该Annotation修饰的Annotation类将被javadoc工具提取成文档

4.@Inherited

用来修饰其他的Annotation的,被修饰过的Annotation将具有继承性。

  1. @xxx是我自定义的注解,我现在使用@xxx注解在Base类上使用….

  2. 使用@Inherited修饰@xxx注解

  3. 当有类继承了Base类的时候,该实现类自动拥有@xxx注解

注解的成员变量

注解上只能定义String、枚举类、Double之类的成员变量

需要注入对象时可在注解里面定义和参数对象一样名字的成员变量,并且提供get,set方法,然后编写自定义编写注入工具。

可看http://www.blogjava.net/jnbzwm/archive/2010/10/04/333720.html自定义注解注入

4)final

  1. final修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法;

  2. final修饰的方法不能被重写;

  3. final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。

5)static 

  1. 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:类名.静态变量名 类名.静态方法名()
  2. 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
  3. 静态内部类(static修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
  4. 静态导包(用来导入类中的静态资源,1.5之后的新特性): 格式为:import static 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值