反射获取构造函数参数,枚举小例子,常量池

getConstructor(String.class,int.class)中的参数是怎么确定?为什么就是String.class和int.class,参数类型为什么又写成(String.class,int.class)这种形式的呢?知道是通过getConstructor方法获得Person类中的构造方法的,Person类中的构造方法是 Person(String name, int age),假如现在不知道Person类中的有参构造方法中的参数有几个呢?,Class是怎么知道这个Person类中有参构造函数的参数个数的?

反射的本质就是将类中所有的类成分映射成对应的Class对象,这里的Class对象其实可以看成一种类型。
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字   void 也表示为 Class 对象。(形如int.class)
参数类型为什么又写成(String.class,int.class)这种形式的呢??这个sun公司就是这么定义的,你知道他表示什么就行了,这是规则。
Constructor  getConstructor(Class<?>... parameterTypes) 
          返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
          Class<?>... parameterTypes指定构造函数的类型,当然如果你传递的是一个空参,那么会调用默认构造函数。
如现在不知道Person类中的有参构造方法中的参数有几个呢?,Class是怎么知道这个Person类中有参构造函数的参数个数的?
第一:你可以这么处理:
Constructor<?>  [] getConstructors() 
          返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法 。Constructor<?>就表示你对应的构函不知道形参类型未知。
第二 :遍历Constructor,利用其提供的方法获取对应构函的信息
Type[]  getGenericParameterTypes() 
         按照声明顺序返回一组 Type 对象,这些对象表示此 Constructor 对象所表示的方法的形参类型。Type[]数组自然会返回你的某个构造函数的参数个数
Class<?>[] getParameterTypes() 
          按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。 

Constructor<?>[] constructors = clazz.getConstructor();
  for(Constructor<?>cons: constructors)
{
    Type[] types=cons.getGenericParameterTypes()
          System.out.println(cons.toString()+types.length);
}

public Constructor<?>[] getConstructors() throws SecurityException

返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。如果该类没有公共构造方法,或者该类是一个数组类,或者该类反映一个基本类型或 void,则返回一个长度为 0 的数组。 注意,此方法返回 Constructor<T> 对象的数组(即取自此类构造方法的数组)时,此方法的返回类型是 Constructor<?>[],不是 预期的 Constructor<T>[]。此少量信息的返回类型是必需的,因为从此方法返回之后,该数组可能被修改以保存不同类的 Constructor 对象,而这将违反 Constructor<T>[] 的类型保证。

这个方法,可以获取到,所有的,公共的构造方法,打印出来,看看就知道有哪些了。

至于那个地方为什么是?.class,  是因为反射机制的运行原理来决定的。反射就是通过解析这些Class文件,知道里面有哪些类,哪些方法。

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

public class EnumTest_6 {

	public static void main(String[] args) {
		System.out.println(TrafficLamp.RED);//当执行TrafficLamp.RED 时编译器具体做了些什么?
		System.out.println(TrafficLamp.GREEN);
		System.out.println(TrafficLamp.YELLOW);
	}

	public enum TrafficLamp {
	//如果TrafficLamp本身是一个类的话,那么 RED,Green,YELLOW又是什么呢,他们和TrafficLamp是什  么关系呢?

		RED(30) {
			public TrafficLamp nextLamp() {
		// nextLamp()这个函数怎么调运,他和 RED是怎么一个关系
			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.当执行TrafficLamp.RED 时编译器具体做了些什么?
编译器调用了TrafficLamp这个枚举的RED元素,并自动实现toString方法。
2.如果TrafficLamp本身是一个类的话,那么 RED,Green,YELLOW又是什么呢,他们和TrafficLamp是什么关系呢?
(1)如果TrafficLamp中没有抽象方法,那么RED,Green,YELLOW就是TrafficLamp的实例对象。
(2)如果TrafficLamp中有抽象方法,那么TrafficLamp不能直接实例化,只有实现了该抽象方法的子类才能实例化,此时RED,Green,YELLOW就是TrafficLamp子类的实例对象。他们和TrafficLamp就是类与对象的关系,抽象与具体的关系。
3. nextLamp()这个函数怎么调运,他和 RED是怎么一个关系。
灯之间的交替关系是联系实际,先确定下来。RED——>GREEN——>YELLOW——>RED这样循环往复。然后通过程序来实现。这个问题,可是使用If——else语句来完成,但这样很麻烦,就用抽象方法 public abstract TrafficLamp nextLamp();来代替,它将大量的if-else语句变成一个个独立的类封装到每个元素中,使结构清晰,易于理解。
4.  public abstract TrafficLamp nextLamp();这行代码有何作用,而且为何要定义成抽象的呢?,而且注释掉 ,没有影响!
作用:它将大量的if-else语句变成一个个独立的类封装到每个元素中,使结构清晰,易于理解。同时这也使用了多态技术,调用时只与基类接口通信,提高程序的扩展性。

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

常量池

常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式;当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。

Java是一种动态链接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值外,还包含一些以文本形式出现的符号引用,比如:
  类和接口的全限定名;
  字段的名称和描述符;
  方法和名称和描述符。

在C语言中,如果一个程序要调用其它库中的函数,在链接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;
而在Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的语句,记录进class文件中的只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。
所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。
具体结构 在Java程序中,有很多的东西是永恒的,不会在运行过程中变化。比如一个类的名字,一个类字段的名字/所属类型,一个类方法的名字/返回类型/参数名与所属类型,一个常量,还有在程序中出现的大量的字面值。


拘留字符串对象
源代码中所有相同字面值的字符串常量只可能建立唯一 一个拘留字符串对象。 实际上JVM是通过一个记录了拘留字符串引用的内部数据结构来维持这一特性的。在Java程序中,可以调用String的intern()方法来使得一个常规字符串对象成为拘留字符串对象。
(1)String s=new String("Hello world"); 编译成class文件后的指令(在myeclipse中查看):
事实上,在运行这段指令之前,JVM就已经为"Hello world"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。
(2)String s="Hello world";
这跟(1)中创建指令有很大的不同,此时局部变量s存储的是早已创建好的拘留字符串的堆地址。


java常量池技术 java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。

  String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术。
  测试代码如下:

public class Test{
	public static void main(String[] args){

	//s1,s2分别位于堆中不同空间
	String s1=new String("hello");
	String s2=new String("hello");
	System.out.println(s1==s2);//输出false
 
	//s3,s4位于池中同一空间
	String s3="hello" String s4="hello";
	System.out.println(s3==s4);//输出true
	}
}
用new String()创建的字符串不是常量,不能在编译期就确定,所以new String()创建的字符串不放入常量池中,他们有自己的地址空间。
String 对象(内存)的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String。 java 为了更有效的使用内存,常量池在编译期遇见String 字符串时,它会检查该池内是否已经存在相同的String 字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。
String a = “test”;
String b = “test”;
String b = b+"java";

a,b同时指向常量池中的常量值"test",b=b+"java"之后,b原先指向一个常量,内容为"test”,通过对b进行+"java" 操作后,b之前所指向的那个值没有改变,但此时b不指向原来那个变量值了,而指向了另一个String变量,内容为”text java“。原来那个变量还存在于内存之中,只是b这个变量不再指向它了。


八种基本类型的包装类和对象池 

Java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。 一些对应的测试代码:

public class Test{ 
	public static void main(String[] args){

	//5种整型的包装类Byte,Short,Integer,Long,Character的对象,
	//在值小于127时可以使用常量池
	Integer i1 = 127;
	Integer i2 = 127;
	System.out.println(i1==i2); //输出true

	//值大于127时,不会从常量池中取对象
	Integer i3 = 128;
	Integer i4 = 128;
	System.out.println(i3==i4); //输出false
	
	//Boolean类也实现了常量池技术
	Boolean bool1 = true;
	Boolean bool2 = true;
	System.out.println(bool1==bool2); //输出true

	//浮点类型的包装类没有实现常量池技术
	Double d1 = 1.0;
	Double d2 = 1.0;
	System.out.println(d1==d2); //输出false
	}
}

//对Integer对象的代码补充
public static Integer valueOf(int i) {

	final int offset = 128;
	if (i >= -128 && i <= 127) {
		return IntegerCache.cache[i + offset];
	}
	return new Integer(i);
}
当你直接给一个Integer对象一个int值的时候,其实它调用了valueOf方法,然后你赋的这个值很特别,是128,那么没有进行cache方法,相当于new了两个新对象。所以问题中定义a、b的两句代码就类似于:
  Integer a = new Integer(128);
  Integer b = new Integer(128);
这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:
  Integer a = 127;
  Integer b = 127;
  System.out.println(a == b);
  结果就是:true
进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。这里引出equals()和==,equals比较的是字符串字面值即比较内容,==比较引用。

看一下IntegerCache这个类里面的内容:

private static class IntegerCache {
  
	private IntegerCache() { }

	static final Integer cache[] = new Integer[-(-128) + 127 + 1];

	static {
		for (int i = 0; i < cache.length; i++)
			cache[i] = new Integer(i - 128);
  	}
}
由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---在IntegerCache.cache中,这样可以在一定程度上提高效率。

针对String方面的补充
在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.

在编译成.class时能够识别为同一字符串的,自动优化成常量,所以也引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,
如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。
所以这时会存在2份拷贝,常量池的部分被String类私有并管理,自己的那份按对象生命周期继续使用。

返回字符串对象的规范化表示形式
一个初始值为空的字符串池,它由类 String 私有地维护。


当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。

------------------------------------代码演示补充-------------------------------------

String s0= "java";
String s1=new String("java");
String s2=new String("java");
s1.intern();
s2=s2.intern(); //把常量池中"java"的引用赋给s2
System.out.println( s0==s1);//false “ intern返回的引用没有引用变量接收~ s1.intern();等于废代码.”
System.out.println( s0==s1.intern() );//true
System.out.println( s0==s2 );//true

String s1=new String("java");
String s2=s1.intern();//s1 检查常量池,发现没有就拷贝自己的字符串进去
//s2 引用该字符串常量池的地址
System.out.println(s2 == s1);//false
System.out.println( s2==s1.intern());//true
System.out.println( s1==s1.intern());// false


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值