Java基础-知识点(一)+https://developer.aliyun.com/article/1625077
hashCode()
对于hashCode的基础知识可以通过以下文章进行学习:
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
EqualExample e1 = new EqualExample(1, 1, 1); EqualExample e2 = new EqualExample(1, 1, 1); System.out.println(e1.equals(e2)); // true HashSet<EqualExample> set = new HashSet<>(); set.add(e1); set.add(e2); System.out.println(set.size()); // 2
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法: 31*x == (x<<5)-x
,编译器会自动进行这个优化。
@Override public int hashCode() { int result = 17; result = 31 * result + x; result = 31 * result + y; result = 31 * result + z; return result; }
toString()
在Java中,toString
方法是一个非常基础且广泛使用的方法,它属于根类java.lang.Object
。每个Java对象都继承自Object
类,因此默认情况下,每个Java对象都有一个toString
方法。
在Object类中,toString方法的默认实现返回一个字符串,该字符串由对象的类名、符号“@”以及对象的哈希码的无符号十六进制表示组成。哈希码是由Object类的hashCode方法生成的,通常用于识别对象。
默认的toString方法实现如下:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
输出:
默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
public class ToStringExample { private int number; public ToStringExample(int number) { this.number = number; } } ToStringExample example = new ToStringExample(123); System.out.println(example.toString()); #输出: ToStringExample@4554617c
案例源码说明
下面是一个简单的类,它没有覆盖toString方法,因此使用的是Object类中的默认实现。
public class DefaultToStringExample { public static void main(String[] args) { DefaultToStringExample example = new DefaultToStringExample(); System.out.println(example.toString()); // 输出类似于 "DefaultToStringExample@79fb3c4" } }
当我们运行上面的代码,System.out.println语句会调用对象的toString方法,并打印出默认的字符串表示。
接下来,我们看一个覆盖了toString方法的类:
public class CustomToStringExample { private int id; private String name; public CustomToStringExample(int id, String name) { this.id = id; this.name = name; } @Override public String toString() { return "CustomToStringExample{" + "id=" + id + ", name='" + name + '\'' + '}'; } public static void main(String[] args) { CustomToStringExample example = new CustomToStringExample(1, "Example Name"); System.out.println(example.toString()); // 输出 "CustomToStringExample{id=1, name='Example Name'}" } }
在这个例子中,我们定义了一个CustomToStringExample类,它有两个属性:id和name。我们通过使用@Override注解覆盖了toString方法,提供了一个更有意义的字符串表示,其中包含了对象的属性信息。
通过覆盖toString方法,我们可以控制对象在字符串表示中的输出格式,这在调试和日志记录中非常有用。
clone()
1. cloneable
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
public class CloneExample { private int a; private int b; } CloneExample e1 = new CloneExample(); // CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'
重写 clone() 得到以下实现:
public class CloneExample { private int a; private int b; @Override protected CloneExample clone() throws CloneNotSupportedException { return (CloneExample)super.clone(); } } CloneExample e1 = new CloneExample(); try { CloneExample e2 = e1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
public class CloneExample implements Cloneable { private int a; private int b; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象。
public class ShallowCloneExample implements Cloneable { private int[] arr; public ShallowCloneExample() { arr = new int[10]; for (int i = 0; i < arr.length; i++) { arr[i] = i; } } public void set(int index, int value) { arr[index] = value; } public int get(int index) { return arr[index]; } @Override protected ShallowCloneExample clone() throws CloneNotSupportedException { return (ShallowCloneExample) super.clone(); } } ShallowCloneExample e1 = new ShallowCloneExample(); ShallowCloneExample e2 = null; try { e2 = e1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } e1.set(2, 222); System.out.println(e2.get(2)); // 222
深拷贝
拷贝对象和原始对象的引用类型引用不同对象。
public class DeepCloneExample implements Cloneable { private int[] arr; public DeepCloneExample() { arr = new int[10]; for (int i = 0; i < arr.length; i++) { arr[i] = i; } } public void set(int index, int value) { arr[index] = value; } public int get(int index) { return arr[index]; } @Override protected DeepCloneExample clone() throws CloneNotSupportedException { DeepCloneExample result = (DeepCloneExample) super.clone(); result.arr = new int[arr.length]; for (int i = 0; i < arr.length; i++) { result.arr[i] = arr[i]; } return result; } } DeepCloneExample e1 = new DeepCloneExample(); DeepCloneExample e2 = null; try { e2 = e1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } e1.set(2, 222); System.out.println(e2.get(2)); // 2
4. clone() 的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
关键字
final
1.数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型,final 使数值不变;
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1; // x = 2; // cannot assign value to final variable 'x' final A y = new A(); y.a = 1;
2.方法
声明方法不能被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
3.类
声明类不允许被继承。
static
1.静态变量
- 静态变量: 又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
- 实例变量: 每创建一个实例就会产生一个实例变量,它与该实例同生共死。
public class A { private int x; // 实例变量 private static int y; // 静态变量 public static void main(String[] args) { // int x = A.x; // Non-static field 'x' cannot be referenced from a static context A a = new A(); int x = a.x; int y = A.y; } }
2.静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法(abstract)。
public abstract class A { public static void func1(){ } // public abstract static void func2(); // Illegal combination of modifiers: 'abstract' and 'static' }
只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
public class A { private static int x; private int y; public static void func1(){ int a = x; // int b = y; // Non-static field 'y' cannot be referenced from a static context // int b = this.y; // 'A.this' cannot be referenced from a static context } }
3.静态语句块
静态语句块在类初始化时运行一次。
public class A { static { System.out.println("123"); } public static void main(String[] args) { A a1 = new A(); A a2 = new A(); } } 123
4.静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要。
public class OuterClass { class InnerClass { } static class StaticInnerClass { } public static void main(String[] args) { // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context OuterClass outerClass = new OuterClass(); InnerClass innerClass = outerClass.new InnerClass(); StaticInnerClass staticInnerClass = new StaticInnerClass(); } }
静态内部类不能访问外部类的非静态的变量和方法。
5.初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
public static String staticField = "静态变量"; static { System.out.println("静态语句块"); } public String field = "实例变量"; { System.out.println("普通语句块"); }
最后才是构造函数的初始化。
public InitialOrderTest() { System.out.println("构造函数"); }
存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
反射
每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java反射机制在框架设计中极为广泛,需要深入理解。本文综合多篇文章后,总结了Java 反射的相关知识,希望可以提升你对Java中反射的认知效率。
反射基础
RTTI(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
这里我们首先需要理解 Class类,以及类的加载机制; 然后基于此我们如何通过反射获取Class类以及类中的成员变量、方法、构造方法等。
Class类
Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName(“类名”)等方法获取class对象)。数组同样也被映射为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement { private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000; private static native void registerNatives(); static { registerNatives(); } /* * Private constructor. Only the Java Virtual Machine creates Class objects. //私有构造器,只有JVM才能调用创建Class对象 * This constructor is not used and prevents the default constructor being * generated. */ private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
到这我们也就可以得出以下几点信息:
- Class类也是类的一种,与class关键字是不一样的。
- 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
- 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
- Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
- Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。
类加载
类加载机制和类字节码技术可以参考如下两篇文章:
- JVM基础 - 类字节码详解
- 源代码通过编译器编译为字节码,再通过类加载子系统进行加载到JVM中运行
- JVM基础 - Java 类加载机制
- 这篇文章将带你深入理解Java 类加载机制
其中,这里我们需要回顾的是:
类加载机制流程
类的加载
反射的使用
我们如何通过反射获取Class类对象以及类中的成员变量、方法、构造方法等?
Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
@Test public void classTest() throws Exception { // 获取Class对象的三种方式 logger.info("根据类名: \t" + User.class); logger.info("根据对象: \t" + new User().getClass()); logger.info("根据全限定类名:\t" + Class.forName("com.test.User")); // 常用的方法 logger.info("获取全限定类名:\t" + userClass.getName()); logger.info("获取类名:\t" + userClass.getSimpleName()); logger.info("实例化:\t" + userClass.newInstance()); } // ... package com.test; public class User { private String name = "init"; private int age; public User() {} public User(String name, int age) { super(); this.name = name; this.age = age; } private String getName() { return name; } private void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }
输出结果:
根据类名: class com.test.User 根据对象: class com.test.User 根据全限定类名: class com.test.User 获取全限定类名: com.test.User 获取类名: User 实例化: User [name=init, age=0]
Class类其他方法:
简单测试下
package com.cry; import java.lang.reflect.Field; interface I1 { } interface I2 { } class Cell{ public int mCellPublic; } class Animal extends Cell{ private int mAnimalPrivate; protected int mAnimalProtected; int mAnimalDefault; public int mAnimalPublic; private static int sAnimalPrivate; protected static int sAnimalProtected; static int sAnimalDefault; public static int sAnimalPublic; } class Dog extends Animal implements I1, I2 { private int mDogPrivate; public int mDogPublic; protected int mDogProtected; private int mDogDefault; private static int sDogPrivate; protected static int sDogProtected; static int sDogDefault; public static int sDogPublic; } public class Test { public static void main(String[] args) throws IllegalAccessException, InstantiationException { Class<Dog> dog = Dog.class; //类名打印 System.out.println(dog.getName()); //com.cry.Dog System.out.println(dog.getSimpleName()); //Dog System.out.println(dog.getCanonicalName());//com.cry.Dog //接口 System.out.println(dog.isInterface()); //false for (Class iI : dog.getInterfaces()) { System.out.println(iI); } /* interface com.cry.I1 interface com.cry.I2 */ //父类 System.out.println(dog.getSuperclass());//class com.cry.Animal //创建对象 Dog d = dog.newInstance(); //字段 for (Field f : dog.getFields()) { System.out.println(f.getName()); } /* mDogPublic sDogPublic mAnimalPublic sAnimalPublic mCellPublic //父类的父类的公共字段也打印出来了 */ System.out.println("---------"); for (Field f : dog.getDeclaredFields()) { System.out.println(f.getName()); } /** 只有自己类声明的字段 mDogPrivate mDogPublic mDogProtected mDogDefault sDogPrivate sDogProtected sDogDefault sDogPublic */ } }
getName、getCanonicalName与getSimpleName的区别:
- getSimpleName:只获取类名
- getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
- getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
package com.cry; public class Test { private class inner{ } public static void main(String[] args) throws ClassNotFoundException { //普通类 System.out.println(Test.class.getSimpleName()); //Test System.out.println(Test.class.getName()); //com.cry.Test System.out.println(Test.class.getCanonicalName()); //com.cry.Test //内部类 System.out.println(inner.class.getSimpleName()); //inner System.out.println(inner.class.getName()); //com.cry.Test$inner System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner //数组 System.out.println(args.getClass().getSimpleName()); //String[] System.out.println(args.getClass().getName()); //[Ljava.lang.String; System.out.println(args.getClass().getCanonicalName()); //java.lang.String[] //我们不能用getCanonicalName去加载类对象,必须用getName //Class.forName(inner.class.getCanonicalName()); 报错 Class.forName(inner.class.getName()); } }
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
下面看一个简单例子来了解Constructor对象的使用:
public class ConstructionTest implements Serializable { public static void main(String[] args) throws Exception { Class<?> clazz = null; //获取Class对象的引用 clazz = Class.forName("com.example.javabase.User"); //第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常 User user = (User) clazz.newInstance(); user.setAge(20); user.setName("Jack"); System.out.println(user); System.out.println("--------------------------------------------"); //获取带String参数的public构造函数 Constructor cs1 =clazz.getConstructor(String.class); //创建User User user1= (User) cs1.newInstance("hiway"); user1.setAge(22); System.out.println("user1:"+user1.toString()); System.out.println("--------------------------------------------"); //取得指定带int和String参数构造函数,该方法是私有构造private Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class); //由于是private必须设置可访问 cs2.setAccessible(true); //创建user对象 User user2= (User) cs2.newInstance(25,"hiway2"); System.out.println("user2:"+user2.toString()); System.out.println("--------------------------------------------"); //获取所有构造包含private Constructor<?> cons[] = clazz.getDeclaredConstructors(); // 查看每个构造方法需要的参数 for (int i = 0; i < cons.length; i++) { //获取构造函数参数类型 Class<?> clazzs[] = cons[i].getParameterTypes(); System.out.println("构造函数["+i+"]:"+cons[i].toString() ); System.out.print("参数类型["+i+"]:("); for (int j = 0; j < clazzs.length; j++) { if (j == clazzs.length - 1) System.out.print(clazzs[j].getName()); else System.out.print(clazzs[j].getName() + ","); } System.out.println(")"); } } } class User { private int age; private String name; public User() { super(); } public User(String name) { super(); this.name = name; } /** * 私有构造 * @param age * @param name */ private User(int age, String name) { super(); this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
输出结果
/* output User{age=20, name='Jack'} -------------------------------------------- user1:User{age=22, name='hiway'} -------------------------------------------- user2:User{age=25, name='hiway2'} -------------------------------------------- 构造函数[0]:private com.example.javabase.User(int,java.lang.String) 参数类型[0]:(int,java.lang.String) 构造函数[1]:public com.example.javabase.User(java.lang.String) 参数类型[1]:(java.lang.String) 构造函数[2]:public com.example.javabase.User() 参数类型[2]:()
关于Constructor类本身一些常用方法如下(仅部分,其他可查API)
代码演示如下:
Constructor cs3 = clazz.getDeclaredConstructor(int.class,String.class); System.out.println("-----getDeclaringClass-----"); Class uclazz=cs3.getDeclaringClass(); //Constructor对象表示的构造方法的类 System.out.println("构造方法的类:"+uclazz.getName()); System.out.println("-----getGenericParameterTypes-----"); //对象表示此 Constructor 对象所表示的方法的形参类型 Type[] tps=cs3.getGenericParameterTypes(); for (Type tp:tps) { System.out.println("参数名称tp:"+tp); } System.out.println("-----getParameterTypes-----"); //获取构造函数参数类型 Class<?> clazzs[] = cs3.getParameterTypes(); for (Class claz:clazzs) { System.out.println("参数名称:"+claz.getName()); } System.out.println("-----getName-----"); //以字符串形式返回此构造方法的名称 System.out.println("getName:"+cs3.getName()); System.out.println("-----getoGenericString-----"); //返回描述此 Constructor 的字符串,其中包括类型参数。 System.out.println("getoGenericString():"+cs3.toGenericString());
输出结果
-----getDeclaringClass----- 构造方法的类:com.example.javabase.User -----getGenericParameterTypes----- 参数名称tp:int 参数名称tp:class java.lang.String -----getParameterTypes----- 参数名称:int 参数名称:java.lang.String -----getName----- getName:com.example.javabase.User -----getoGenericString----- getoGenericString():private com.example.javabase.User(int,java.lang.String)
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
下面的代码演示了上述方法的使用过程
public class ReflectField { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class<?> clazz = Class.forName("reflect.Student"); //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段, // 否则抛NoSuchFieldException Field field = clazz.getField("age"); System.out.println("field:"+field); //获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取 Field fields[] = clazz.getFields(); for (Field f:fields) { System.out.println("f:"+f.getDeclaringClass()); } System.out.println("================getDeclaredFields===================="); //获取当前类所字段(包含private字段),注意不包含父类的字段 Field fields2[] = clazz.getDeclaredFields(); for (Field f:fields2) { System.out.println("f2:"+f.getDeclaringClass()); } //获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段 Field field2 = clazz.getDeclaredField("desc"); System.out.println("field2:"+field2); } /** 输出结果: field:public int reflect.Person.age f:public java.lang.String reflect.Student.desc f:public int reflect.Person.age f:public java.lang.String reflect.Person.name ================getDeclaredFields==================== f2:public java.lang.String reflect.Student.desc f2:private int reflect.Student.score field2:public java.lang.String reflect.Student.desc */ } class Person{ public int age; public String name; //省略set和get方法 } class Student extends Person{ public String desc; private int score; //省略set和get方法 }
上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:
//获取Class对象引用 Class<?> clazz = Class.forName("reflect.Student"); Student st= (Student) clazz.newInstance(); //获取父类public字段并赋值 Field ageField = clazz.getField("age"); ageField.set(st,18); Field nameField = clazz.getField("name"); nameField.set(st,"Lily"); //只获取当前类的字段,不获取父类的字段 Field descField = clazz.getDeclaredField("desc"); descField.set(st,"I am student"); Field scoreField = clazz.getDeclaredField("score"); //设置可访问,score是private的 scoreField.setAccessible(true); scoreField.set(st,88); System.out.println(st.toString()); //输出结果:Student{age=18, name='Lily ,desc='I am student', score=88} //获取字段值 System.out.println(scoreField.get(st)); // 88
其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()
、setBoolean()/getBoolean
、setChar()/getChar()
等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
同样通过案例演示上述方法:
import java.lang.reflect.Method; public class ReflectMethod { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Class clazz = Class.forName("reflect.Circle"); //根据参数获取public的Method,包含继承自父类的方法 Method method = clazz.getMethod("draw",int.class,String.class); System.out.println("method:"+method); //获取所有public的方法: Method[] methods =clazz.getMethods(); for (Method m:methods){ System.out.println("m::"+m); } System.out.println("========================================="); //获取当前类的方法包含private,该方法无法获取继承自父类的method Method method1 = clazz.getDeclaredMethod("drawCircle"); System.out.println("method1::"+method1); //获取当前类的所有方法包含private,该方法无法获取继承自父类的method Method[] methods1=clazz.getDeclaredMethods(); for (Method m:methods1){ System.out.println("m1::"+m); } } } class Shape { public void draw(){ System.out.println("draw"); } public void draw(int count , String name){ System.out.println("draw "+ name +",count="+count); } } class Circle extends Shape{ private void drawCircle(){ System.out.println("drawCircle"); } public int getAllCount(){ return 100; } }
输出结果:
method:public void reflect.Shape.draw(int,java.lang.String) m::public int reflect.Circle.getAllCount() m::public void reflect.Shape.draw() m::public void reflect.Shape.draw(int,java.lang.String) m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException m::public final void java.lang.Object.wait() throws java.lang.InterruptedException m::public boolean java.lang.Object.equals(java.lang.Object) m::public java.lang.String java.lang.Object.toString() m::public native int java.lang.Object.hashCode() m::public final native java.lang.Class java.lang.Object.getClass() m::public final native void java.lang.Object.notify() m::public final native void java.lang.Object.notifyAll() ========================================= method1::private void reflect.Circle.drawCircle() m1::public int reflect.Circle.getAllCount() m1::private void reflect.Circle.drawCircle()
在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods
方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:
Class clazz = Class.forName("reflect.Circle"); //创建对象 Circle circle = (Circle) clazz.newInstance(); //获取指定参数的方法对象Method Method method = clazz.getMethod("draw",int.class,String.class); //通过Method对象的invoke(Object obj,Object... args)方法调用 method.invoke(circle,15,"圈圈"); //对私有无参方法的操作 Method method1 = clazz.getDeclaredMethod("drawCircle"); //修改私有方法的访问标识 method1.setAccessible(true); method1.invoke(circle); //对有返回值得方法操作 Method method2 =clazz.getDeclaredMethod("getAllCount"); Integer count = (Integer) method2.invoke(circle); System.out.println("count:"+count);
输出结果
draw 圈圈,count=15 drawCircle count:100
在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)
第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
反射机制的执行过程
后面补充。。。
异常
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception。其中 Error 用来表示 JVM 无法处理的错误,Exception 分为两种:
- 受检异常 : 需要用 try…catch… 语句捕获并进行处理,并且可以从异常中恢复;
- 非受检异常 : 是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序崩溃并且无法恢复。
泛型
public class Box<T> { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
相关文章:Java 基础 - 泛型机制详解
注解
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
相关文章:Java 基础 - 注解机制详解
特性
Java 各版本的新特性
New highlights in Java SE 8
- Lambda Expressions
- Pipelines and Streams
- Date and Time API
- Default Methods
- Type Annotations
- Nashhorn JavaScript Engine
- Concurrent Accumulators
- Parallel operations
- PermGen Error Removed
New highlights in Java SE 7
- Strings in Switch Statement
- Type Inference for Generic Instance Creation
- Multiple Exception Handling
- Support for Dynamic Languages
- Try with Resources
- Java nio Package
- Binary Literals, Underscore in literals
- Diamond Syntax
Java 与 C++ 的区别
- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
- Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
- Java 支持自动垃圾回收,而 C++ 需要手动回收。
- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
- Java 不支持操作符重载,虽然可以对两个 String 对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
- Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。
What are the main differences between Java and C++?在新窗口打开
JRE or JDK
- JRE is the JVM program, Java application need to run on JRE.
- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler “javac”