目录
一、JAVA基本数据类型及其包装类型
二、泛型
三、面向对象的三大特性
四、面向对象与面向过程的区别
五、JDK、JRE、JVM的关系
六、重载和重写
七、构造方法
八、JAVA中创建对象的方式
九、抽象类和接口
十、Object类的常用方法
十一、final、finally、finalize
十二、== 与 equals
十三、hashCode() 与 equals()
十四、实现对象的克隆,深拷贝和浅拷贝
十五、JAVA序列化
十六、反射
十七、动态代理
十八、字节与字符、String为什么不可变。
十九、String,StringBuilder,StringBuffer
二十、异常:Error与Exception、运行时异常与受检异常、throw与throws
二十一、主线程可以捕获到子线程的异常吗
二十二、JAVA中IO流的分类、常用的实现类、字节流和字符流、获取键盘输入
二十三、BIO、NIO、AIO
二十四、成员变量与局部变量
二十五、静态方法和实例方法
二十六、JAVA中值传递
二十七、JAVA中线程的基本状态
二十八、final关键字、static关键字
二十九、Throwable类常用方法
三十、try-catch-finally
三十一、匿名函数 Lambda 表达式
三十二、获得Class对象的方法
三十三、java语言的特点
三十四、杂项
基本数据类型 | 所占位数 | 默认值 | 对应包装类型 |
boolean |
1 | false | Boolean |
byte | 8 | 0 | Byte |
char | 16 | '\u0000' | Character |
short | 16 | 0 | Short |
int | 32 | 0 | Integer |
long | 64 | 0L | Long |
float | 32 | 0.0f | Float |
double | 64 | 0.0d | Double |
注意事项:
(1)变量必须先声明再赋值才能访问。
成员变量没有手动赋值系统会默认赋值,局部变量不会。
(2)
小容量可以自动转换成大容量,成为自动类型转换
大容量不能直接赋值给小容量,需要用强制类型转换符强制转换-->直接砍掉高位,取低位
(3)字面量:
整数型字面值默认为int类型。
字面量为 long 类型 --> 123L,123l
字面量为二进制,八进制,十六进制 --> 0b或者0B,0,0x或者0X
当一个整数字面值没有超出byte,short,char的取值范围,这个字面值可以直接赋值给byte,short,char类型变量
浮点型字面值默认是double类型,float x = 1.23 编译错误 --> float x = 1.23f 或者 float x = 1.23F
boolean类型只能是 true 或者 false
(4)八种基本数据类型当中除 boolean 之外剩下的 7 种类型之间都可以相互转换。
小容量向大容量转换,称为自动类型转换 --> 整型一定小于浮点型,char 与 short 平级
byte < short = char < int < long < float < double
大容量转换成小容量,强制类型转换 --> 需加强制类型转换符
byte,short,char 参与运算的时候,各自先转换成 int 类型再做运算
多种数据类型混合运算,先转换成容量最大的那种类型再做运算。
(5)包装类构造方式
① 通过直接赋值 Integer x=1; --> 自动装箱
自动装箱 Integer.valueOf() ; 自动拆箱 x.intValue()
② 构造器 Integer x=new Integer(1);
③ 静态工厂valueOf 方法 Integer x=Integer.valueOf(1);
new Integer(1) 会新建一个对象并返回引用
Integer.valueOf(1)会使用整型常量池,范围默认为 [ -128 , 127 ]
(6) int x = Integer.parseInt("123")与int x = new Integer("123")区别
两个方式都是将字符串类型转变成int类型。第一个直接将字符串转变成int类型。第二个通过调用构造函数方式,先将字符串构建成Integer对象,再通过自动拆包转变成int类型。
(7)
Boolean :使用静态final定义,就会返回静态值;
Byte :缓存区 -128~127,全部缓存;
Short :缓存区 -128~127,部分缓存;
Character :缓存区0~127,部分缓存;
Long :缓存区-128~127,部分缓存;
Integer :缓存区-128~127,部分缓存
Float 和Double不会有缓存
Integer是唯一可以修改缓存范围的包装类。只能修改 high 且 high 小于127时不生效。
(8)switch
Only convertible int values, strings or enum variables are permitted
其中 byte,short,char 可以自动转换为 int,所以可以使用。
(9)
当 Integer 和 int 进行比较时,Integer 会自动拆箱为 int。因此就相当于两个 int 比较。
当两个 Integer 相算术运算时,会先拆箱为 int,运算结果为 int 数据类型
(10)
Set set = new HashSet<>();
for(short i = 0;i<5;i++){
set.add(i);
set.remove(i-1);
}
System.out.println(set.size());
答案:5
解析:short类型-1之后就变成了int类型,remove()的时候在集合中找不到int类型的数据,所以就没有删除任何元素。
(11)
short s=2; s=s+1; 编译错误, s+1 操作后,数据类型变为 int 类型,int 类型的数据不能直接赋值给 short 类型
short s=2; s+=1; 正确,s+=1 等价于 s = (short)(s+1)
(12)
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ?
Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
(13)
(1)泛型类的定义与使用
修饰符 class 类名<代表泛型的变量> { }
//定义
public class Generic {
private T ans;
public Generic(T ans){
this.ans = ans;
}
public void setAns(T ans) {
this.ans = ans;
}
public T getAns() {
return ans;
}
}
//使用
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Generic gen1 = new Generic<>("abc");
System.out.println(gen1.getAns());
Generic> gen2 = new Generic<>(new ArrayList<>());
gen2.getAns().add(1);
gen2.getAns().add(2);
gen2.getAns().add(3);
System.out.println(gen2.getAns().size());
}
}
类型变量使用大写形式,且比较短, 这是很常见的。在 Java 库中, 使用变量 E 表示集合的元素类型, K 和 V 分别表示表的关键字与值的类型。T ( 需要时还可以用临近的字母 U 和 S) 表示“ 任意类型”。
(2)泛型方法的定义与使用
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
class Array{
public static T getArrayMid(T...ts) {
return ts[ts.length/2];
}
}
这个方法是在普通类中定义的,而不是在泛型类中定义的。然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
当调用一个泛型方法时在方法名前的尖括号中放人具体的类型:String ans = Array.
在这种情况(实际也是大多数情况)下,方法调用中可以省略
public class Main {
public static void main(String[] args) {
String ans = Array.getArrayMid("1","2","3","4","5");
System.out.println(ans);
Integer num = Array.getArrayMid(1,2,3,4,5,6,7,8);
System.out.println(num);
String ans2 = Array.getArrayMid("1","2","3","4","5");
System.out.println(ans2);
Integer num2 = Array.getArrayMid(1,2,3,4,5,6,7,8);
System.out.println(num2);
}
}
(3)类型变量的限定
有时,类或方法需要对类型变量加以约束。下面是一个典型的例子。我们要计算数组中的最小元素:
class ArrayAIg
{
public static T min(T[] a) // almost correct
{
if (a null || a.length = 0) return null ;
T smallest = a[0];
for (int i = 1; i < a.length; i ++)
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
return smallest;
}
}
但是,这里有一个问题。请看一下 min方法的代码内部。 变量 smallest 类型为 T, 这意味着它可以是任何一个类的对象。怎么才能确信 T 所属的类有 compareTo 方法呢?
解决这个问题的方案是将 T 限制为实现了 Comparable 接口(只含一个方法 compareTo 的
标准接口)的类。可以通过对类型变量 T 设置限定(bound) 实现这一点:
class ArrayAlg{
public static T min(T[] a) {
if(a==null || a.length==0) {
return null;
}
T min = a[0];
for(int i = 1; i < a.length; i++) {
if(min.compareTo(a[i]) > 0) min = a[i];
}
return min;
}
}
实际上 Comparable 接口本身就是一个泛型类型。目前, 我们忽略其复杂性以及编译器产生的警告。现在,泛型的 min方法只能被实现了 Comparable 接口的类(如 String、 LocalDate 等)的数组调用。
在此为什么使用关键字 extends 而不是 implements ? 毕竟,Comparable 是一个接口。下面的记法
一个类型变量或通配符可以有多个限定, 例如: T extends Comparable & Serializable限定类型用“ &” 分隔,而逗号用来分隔类型变量。
在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用
一个类作为限定,它必须是限定列表中的第一个。
(4)泛型擦除
1.虚拟机中没有泛型,只有普通的类和方法。
无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型 ( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变M, 并替换为限定类型(无限定的变量用 Object)。结果是一个普通的类, 就好像泛型引人 Java 语言之前已经实现的那样。原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。
public class Interval
2.所有的类型参数都用它们的限定类型替换。
当程序调用泛型方法时,如果擦除返回类型, 编译器插入强制类型转换。例如,下面这个语句序列
Pair
Employee buddy = buddies.getFirst();
擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:
•对原始方法 Pair.getFirst 的调用。
•将返回的 Object 类型强制转换为 Employee 类型。
当存取一个泛型域时也要插人强制类型转换。假设 Pair 类的 first 域和 second 域都是公有的(也许这不是一种好的编程风格,但在 Java 中是合法的)。表达式:Employee buddy = buddies.first; 也会在结果字节码中插人强制类型转换。
3.桥方法被合成来保持多态。
set
Datelnterval 重写Pair的set方法并指定参数类型
编译器在 Datelnterval 类中生成一个桥方法(bridge method):
public void setSecond(Object second) { setSecond((Date) second); }
get
具有相同参数类型的两个方法是不合法的。两个类的get方法它们都没有参数。但是,在虚拟机中,用参数类型和返回类型确定一个方法。因此, 编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况。
桥方法不仅用于泛型类型。 在一个方法覆盖另一个方法时可以指定一个更严格的返回类型。例如:
public class Employee implements Cloneable
{
public Employee clone() throws CloneNotSupportedException { . . . }
}
Object.clone 和 Employee.clone 方法被说成具有协变的返回类型 (covariant return types)。
实际上,Employee 类有两个克隆方法:
Employee clone() // defined above
Object clone() // synthesized bridge method, overrides Object,clone
合成的桥方法调用了新定义的方法
4.为保持类型安全性,必要时插人强制类型转换
(5)约束与局限性
2.运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
例如:
if (a instanceof Pair
实际上仅仅测试 a 是否是任意类型的一个 Pair。下面的测试同样如此:
if (a instanceof Pair
或强制类型转换:
Pair
为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
同样的道理, getClass 方法总是返回原始类型。例如:
Pair
Pair
if (stringPair.getClass() == employeePair.getClass()) // they are equal
其比较的结果是 true, 这是因为两次调用 getClass 都将返回 Pair.class。
3.不能创建参数化类型的数组
不能实例化参数化类型的数组, 例如:
Pair
这有什么问题呢? 擦除之后, table 的类型是 Pair[]。可以把它转换为 Object[] :
Object[] objarray = table;
数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个ArrayStoreException 异常:
objarray[0] = "Hello"; // Error component type is Pair
不过对于泛型类型, 擦除会使这种机制无效。以下赋值:
objarray[0] = new Pair
能够通过数组存储检査, 不过仍会导致一个类型错误。出于这个原因, 不允许创建参数化类型的数组。需要说明的是, 只是不允许创建这些数组, 而声明类型为 Pair
可以声明通配类型的数组, 然后进行类型转换:Pair
向参数个数可变的方法传递一个泛型类型的实例。
考虑下面这个简单的方法, 它的参数个数是可变的:
public static
for (t : ts) coll.add(t)
}
应该记得,实际上参数 ts 是一个数组, 包含提供的所有实参。
现在考虑以下调用:
Col1ection
Pair
Pair
addAll(table, pairl, pair2);
为了调用这个方法,Java 虚拟机必须建立一个 Pair
可以采用两种方法来抑制这个警告。一种方法是为包含 addAll 调用的方法增加注解 @SuppressWamings("unchecked")。或者在 Java SE 7中, 还 可 以 用@SafeVarargs 直 接 标 注addAll 方法:@SafeVarargs public static
现在就可以提供泛型类型来调用这个方法了。对于只需要读取参数数组元素的所有方法,都可以使用这个注解,这仅限于最常见的用例。
可以使用 @SafeVarargs 标注来消除创建泛型数组的有关限制, 方法如下:
@SafeVarargs static
现在可以调用:
Pair
这看起来彳艮方便,不过隐藏着危险。以下代码:
Object[] objarray = table;
objarray[0] = new Pair
能顺利运行而不会出现 ArrayStoreException 异常(因为数组存储只会检查擦除的类型 ),但在处理 table[0] 时你会在别处得到一个异常。
5.不能实例化类型变量
不能使用像 new T(...),newT[...] 或 T.class 这样的表达式中的类型变量。例如, 下面的Pair
public Pair() { first = new T(); second = new T(); } // Error
类型擦除将 T 改变成 Object, 而且, 本意肯定不希望调用 new Object()。在 Java SE 8 之后,最好的解决办法是让调用者提供一个构造器表达式。例如:
Pair
makePair 方法接收一个 Supplier
public static
return new Pair<>(constr.get(), constr.get());
}
比较传统的解决方法是通过反射调用 Clasmewlnstance 方法来构造泛型对象。遗憾的是,细节有点复杂。不能调用:
first = T.dass.newInstanceO; // Error
表达式 T.class 是不合法的, 因为它会擦除为 Objectclass。必须像下面这样设计 API 以便得到一个 Class 对象:
public static
try {
return new Pair(d.newInstance(), cl.newInstance());
}catch (Exception ex) { return null; }
}
这个方法可以按照下列方式调用
Pair
注意,Class类本身是泛型。 例如,String.class 是一个 Class
6.不能构造泛型数组
就像不能实例化一个泛型实例一样, 也不能实例化数组。不过原因有所不同,毕竟数组会填充 null 值,构造时看上去是安全的。不过, 数组本身也有类型,用来监控存储在虚拟机中的数组。这个类型会被擦除。 例如,考虑下面的例子:
public static
类型擦除会让这个方法永远构造 Comparable[2] 数组。如果数组仅仅作为一个类的私有实例域, 就可以将这个数组声明为 Object[],并且在获取元素时进行类型转换。
让用户提供一个数组构造器表达式:
String[] ss = ArrayAlg.minmax (String[]::new,"Tom", "Dick", "Harry");
构造器表达式 String[]::new 指示一个函数, 给定所需的长度, 会构造一个指定长度的String 数组。minmax 方法使用这个参数生成一个有正确类型的数组:
public static
T[] mm = constr.apply(2);
}
比较老式的方法是利用反射, 调用 Array.newlnstance:
public static
T[] mm = (T[]) Array.newlnstance (a.getClass().getComponentType() , 2);
}
ArrayList 类的 toArray 方法就没有这么幸运。它需要生成一个 T[] 数组, 但没有成分类型。因此, 有下面两种不同的形式:
Object[] toArray()
T[] toArray(T[] result)
第二个方法接收一个数组参数。如果数组足够大, 就使用这个数组。 否则, 用 result 的成分类型构造一个足够大的新数组。
还可以:
java.lang.reflect.Array.newInstance(Class componentType, int length)方法来创建一个具有指定类型和维度的数组。
7.泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。例如, 下列高招将无法施展:
public class Singleton
private static T singlelnstance; // Error
public static T getSinglelnstance() // Error
{
if (singleinstance == null) construct new instance of T
return singlelnstance;
}
}
如果这个程序能够运行, 就可以声明一个 Singleton
8.不能抛出或捕获泛型类的实例
catch 子句中不能使用类型变量。例如, 以下方法将不能编译:
public static
{
try
{
do work
}
catch (T e) // Error can 't catch type variable
{
Logger,global.info(...)
}
}
不过, 在异常规范中使用类型变量是允许的。以下方法是合法的:
public static
{ // OK
try
{
do work
}
catch (Throwable real Cause)
{
t.initCause(real Cause);
throw t;
}
}
9.可以消除对受查异常的检查
10.注意擦除后的冲突
泛型规范说明还提到另外一个原则:
(9)使用 Class<T> 参数进行类型匹配
List intArray; // compile error
List intArray; // compile success
List intArray; // compile error
List intArray; // compile success
public void testMethod(List array) {}
public void testMethod(List array) {} // compile error
c.泛型类型无法当做真实类型使用
static void genericMethod(T t) {
T newInstance = new T(); // compile errror
Class c = T.class; // compile errror
List list = new ArrayList(); // compile errror
if (list instance List) {} // compile errror
}
d.静态方法无法引用类泛型参数
class GenericClass {
public static T max(T a, T b) {}
}
e.泛型类型会带来类型强转的运行时开销
List strList = new Array<>();
strList.add("Hallo");
String value = strList.get(0);
但实际字节码指令执行strList.get()
方法时,经过类型擦除后,还是需要做类型强转:
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
CHECKCAST java/lang/String
(12)类型擦除后怎么获取泛型参数?
class GenericClass {}
class ConcreteClass extends GenericClass {
public List getArray() {}
}
// 获取类元素泛型
ParameterizedType genericType =
(ParameterizedType)ConcreteClass.class.getGenericSuperClass();
// 获取方法元素泛型
ParameterizedType genericType =
(ParameterizedType)ConcreteClass.class.getMethod("getArray").getGenericReturnTypes();
综上:重载就是同⼀个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
区别点
|
重载方法 | 重写方法 |
发生范围 | 同⼀个类 | 子类 |
参数列表 | 必须修改 | ⼀定不能修改 |
返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
访问修饰符 | 可修改 | ⼀定不能做更严格的限制(可以降低限制) |
发生阶段 | 编译期 | 运⾏期 |
两个对象的 hashCode() 相同,equals() 不⼀定为 true。因为在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不⼀定能得出键值对相等【散列冲突】。
一致性(consistent),在程序的一次执行过程中,对同一个对象必须一致地返回同一个整数。
如果两个对象通过equals(Object)
比较,结果相等,那么对这两个对象分别调用hashCode
方法应该产生相同的整数结果。(PS:这里equals
和hashCode
说的都是Object
类的)
如果两个对象通过java.lang.Object.equals(java.lang.Ojbect)
比较,结果不相等,不必保证对这两个对象分别调用hashCode
也返回两个不相同的整数。
(一)Class类
Class是一个类,封装了当前对象所对应的类的信息
对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。
Class 对象只能由系统建立对象,一个类(而不是一个对象)在 JVM 中只会有一个Class实例
获取Class对象的三种方式:
1.通过类名获取 类名.class
2.通过对象获取 对象名.getClass()
3.通过全类名获取 Class.forName(全类名)
Class类的常用方法:
方法名 |
功能说明 |
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
Object newInstance(Object []args) | 调用当前格式构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
(二)ClassLoader
类装载器是用来把类(class)装载进 JVM 的。
public class ReflectionTest {
@Test
public void testClassLoader() throws ClassNotFoundException, FileNotFoundException{
//1. 获取一个系统的类加载器(可以获取,当前这个类PeflectTest就是它加载的)
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader);
//2. 获取系统类加载器的父类加载器(扩展类加载器,可以获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);
//3. 获取扩展类加载器的父类加载器(引导类加载器,不可获取).
classLoader = classLoader.getParent();
System.out.println(classLoader);
//4. 测试当前类由哪个类加载器进行加载(系统类加载器):
classLoader = Class.forName("com.atguigu.java.fanshe.ReflectionTest")
.getClassLoader();
System.out.println(classLoader);
//5. 测试 JDK 提供的 Object 类由哪个类加载器负责加载(引导类)
classLoader = Class.forName("java.lang.Object")
.getClassLoader();
System.out.println(classLoader);
}
}
//结果:
//sun.misc.Launcher$AppClassLoader@5ffdfb42
//sun.misc.Launcher$ExtClassLoader@1b7adb4a
//null
//sun.misc.Launcher$AppClassLoader@5ffdfb42
//null
(三)反射
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。
Java反射机制主要提供了以下功能:
Class 是一个类; 一个描述类的类.
封装了描述方法的 Method,
封装了描述字段的 Filed,
封装了描述构造器的 Constructor 等属性.
(1)Method
public class ReflectionTest {
@Test
public void testMethod() throws Exception{
Class clazz = Class.forName("com.atguigu.java.fanshe.Person");
//
//1.获取方法
// 1.1 获取取clazz对应类中的所有方法--方法数组(一)
// 不能获取private方法,且获取从父类继承来的所有方法(不包括父类的私有方法)
Method[] methods = clazz.getMethods();
for(Method method:methods){
System.out.print(" "+method.getName());
}
System.out.println();
//
// 1.2.获取所有方法,包括私有方法 --方法数组(二)
// 所有声明的方法,都可以获取到,且只获取当前类的方法
methods = clazz.getDeclaredMethods();
for(Method method:methods){
System.out.print(" "+method.getName());
}
System.out.println();
//
// 1.3.获取指定的方法
// 需要参数名称和参数列表,无参则不需要写
// 对于方法public void setName(String name) { }
Method method = clazz.getDeclaredMethod("setName", String.class);
System.out.println(method);
// 而对于方法public void setAge(int age) { }
method = clazz.getDeclaredMethod("setAge", Integer.class);
System.out.println(method);
// 这样写是获取不到的,如果方法的参数类型是int型
// 如果方法用于反射,那么要么int类型写成Integer: public void setAge(Integer age){}
// 要么获取方法的参数写成int.class
//
// 2.执行方法
// invoke第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数
Object obje = clazz.newInstance();
method.invoke(obje,2);
//如果一个方法是私有方法,第三步是可以获取到的,但是这一步却不能执行
//私有方法的执行,必须在调用invoke之前加上一句 method.setAccessible(true);
}
}
setAccessible()
将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问 ;
由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
主要用到两个方法:
/**
* @param name the name of the method
* @param parameterTypes the list of parameters
* @return the {@code Method} object that matches the specified
*/
public Method getMethod(String name, Class>... parameterTypes){
}
/**
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
* @return the result of dispatching the method represented by
*/
public Object invoke(Object obj, Object... args){
}
(2)Field
@Test
public void testField() throws Exception{
String className = "com.atguigu.java.fanshe.Person";
Class clazz = Class.forName(className);
//1.获取字段
// 1.1 获取所有字段 -- 字段数组
// 可以获取公用和私有的所有字段,但不能获取父类字段
Field[] fields = clazz.getDeclaredFields();
for(Field field: fields){
System.out.print(" "+ field.getName());
}
System.out.println();
// 1.2获取指定字段
Field field = clazz.getDeclaredField("name");
System.out.println(field.getName());
Person person = new Person("ABC",12);
//2.使用字段
// 2.1获取指定对象的指定字段的值
Object val = field.get(person);
System.out.println(val);
// 2.2设置指定对象的指定对象Field值
field.set(person, "DEF");
System.out.println(person.getName());
// 2.3如果字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法
// 比如Person类中,字段name字段是公用的,age是私有的
field = clazz.getDeclaredField("age");
field.setAccessible(true);
field.set(person, 13);
System.out.println(field.get(person));
}
(3)Constructor
@Test
public void testConstructor() throws Exception{
String className = "com.atguigu.java.fanshe.Person";
Class clazz = (Class) Class.forName(className);
//1. 获取 Constructor 对象
// 1.1 获取全部
Constructor [] constructors =
(Constructor[]) Class.forName(className).getConstructors();
for(Constructor constructor: constructors){
System.out.println(constructor);
}
// 1.2获取某一个,需要参数列表
Constructor constructor = clazz.getConstructor(String.class, int.class);
System.out.println(constructor);
//2. 调用构造器的 newInstance() 方法创建对象
Object obj = constructor.newInstance("zhagn", 1);
}
(4)总结:
1. Class: 是一个类; 一个描述类的类.
封装了描述方法的 Method,
描述字段的 Filed,
描述构造器的 Constructor 等属性.
2. 如何得到 Class 对象:
2.1 Person.class
2.2 person.getClass()
2.3 Class.forName("com.atguigu.javase.Person")
3. 关于 Method:
3.1 如何获取 Method:
1). getDeclaredMethods: 得到 Method 的数组.
2). getDeclaredMethod(String methondName, Class ... parameterTypes)
3.2 如何调用 Method
1). 如果方法时 private 修饰的, 需要先调用 Method 的 setAccessible(true), 使其变为可访问
2). method.invoke(obj, Object ... args);
4. 关于 Field:
4.1 如何获取 Field: getField(String fieldName)
4.2 如何获取 Field 的值:
1). setAccessible(true)
2). field.get(Object obj)
4.3 如何设置 Field 的值:
field.set(Obejct obj, Object val)
5. 了解 Constructor 和 Annotation
6. 反射和泛型.
6.1 getGenericSuperClass: 获取带泛型参数的父类, 返回值为: BaseDao
6.2 Type 的子接口: ParameterizedType
6.3 可以调用 ParameterizedType 的 Type[] getActualTypeArguments() 获取泛型参数的数组.
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
public interface UserService {
public void select();
public void update();
}
public class UserServiceImpl implements UserService {
public void select() {
System.out.println("查询 selectById");
}
public void update() {
System.out.println("更新 update");
}
}
select
和 update
之前记录一些日志。写一个代理类 UserServiceProxy,代理类需要实现 UserService
public class UserServiceProxy implements UserService {
private UserService target; // 被代理的对象
public UserServiceProxy(UserService target) {
this.target = target;
}
public void select() {
before();
target.select(); // 这里才实际调用真实主题角色的方法
after();
}
public void update() {
before();
target.update(); // 这里才实际调用真实主题角色的方法
after();
}
private void before() { // 在执行方法之前执行
System.out.println(String.format("log start time [%s] ", new Date()));
}
private void after() { // 在执行方法之后执行
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
public class Client1 {
public static void main(String[] args) {
UserService userServiceImpl = new UserServiceImpl();
UserService proxy = new UserServiceProxy(userServiceImpl);
proxy.select();
proxy.update();
}
}
log start time [Thu Dec 20 14:13:25 CST 2018]
查询 selectById
log end time [Thu Dec 20 14:13:25 CST 2018]
log start time [Thu Dec 20 14:13:25 CST 2018]
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018]
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
静态代理的缺点
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
为什么类可以动态的生成?
这就涉及到Java虚拟机的类加载机制了,推荐翻看《深入理解Java虚拟机》7.3节 类加载的过程。
Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:
java.lang.Class
对象,作为方法区这个类的各种数据访问入口由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:
*$Proxy
的代理类的二进制字节流所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用。
(二)JDK动态代理
java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
,我们仍然通过案例来学习
invoke
方法中编写方法调用的逻辑处理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client2 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
// 设置变量可以保存动态代理类,默认名称以 $Proxy0 格式命名
// System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
5.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();
// 保存JDK动态代理生成的代理类,类名保存为 UserServiceProxy
// ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
}
}
(3)运行结果
log start time [Thu Dec 20 16:55:19 CST 2018]
查询 selectById
log end time [Thu Dec 20 16:55:19 CST 2018]
log start time [Thu Dec 20 16:55:19 CST 2018]
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018]
InvocationHandler 和 Proxy 的主要方法介绍如下:
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static InvocationHandler getInvocationHandler(Object proxy)
用于获取指定代理对象所关联的调用处理器
static Class> getProxyClass(ClassLoader loader, Class>... interfaces)
返回指定接口的代理类
static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
static boolean isProxyClass(Class> cl)
返回 cl 是否为一个代理类
从 UserServiceProxy 的代码中我们可以发现:
public final
修饰,所以代理类只可被使用,不可以再被继承m + 数字
的格式命名super.h.invoke(this, m1, (Object[])null);
调用,其中的 super.h.invoke
实际上是在创建代理的时候传递给 Proxy.newProxyInstance
的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方法
after();
return result; // 返回方法的执行结果
}
(6)
描述动态代理的几种实现方式?分别说出相应的优缺点
代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK动态代理" 和 "CGLIB动态代理" 实现。
静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object
JDK 动态代理:
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
CGLIB 代理:
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK方式解决不了;
CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
@Overridepublic StringBuffer insert(int dstOffset, CharSequence s) {
// Note, synchronization achieved via invocations of other StringBuffer methods
// after narrowing of s to specific type
// Ditto for toStringCache clearing
super.insert(dstOffset, s);
return this;
}
public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str3 == str4); // false
System.out.println(str3.equals(str4)); // true
}
}
package constxiong.interview;
public class TestOmitTryCatchFinally {
public static void main(String[] args) {
omitFinally();
omitCatch();
}
/**
* 省略finally 语句块
*/
public static void omitFinally() {
try {
int i = 0;
i += 1;
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 省略 catch 语句块
*/
public static void omitCatch() {
int i = 0;
try {
i += 1;
} finally {
i = 10;
}
System.out.println(i);
}
}
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
⽅法 2:通过 BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
按操作对象分类结构图:
线程在⽣命周期中并不是固定处于某⼀个状态而是随着代码的执行在不同状态之间切换
try、catch、finally用法总结:
1、不管有没有异常,finally中的代码都会执行
2、当try、catch中有return时,finally中的代码依然会继续执行
3、程序在执行到 return 时会首先将返回值存储在⼀个指定的位置,其次去执行 finally 块,最后再返回。因此, 对基本数据类型,在 finally 块中改变 return 的值没有任何影响,直接覆盖掉;而对引用类型是有影响的,返回的 是 finally 对 前面 return 语句返回对象的修改值。
4、finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda 表达式的语法格式如下:
(parameters) -> expression 或 (parameters) ->{ statements; }
以下是lambda表达式的重要特征:
Lambda 表达式的简单例子:
// 1. 不需要参数,返回值为 5 () -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值 (x, y) -> x – y // 4. 接收2个int型整数,返回他们的和 (int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) (String s) -> System.out.print(s)
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
$ javac Java8Tester.java $ java Java8Tester 10 + 5 = 15 10 - 5 = 5 10 x 5 = 50 10 / 5 = 2 Hello Runoob Hello Google
使用 Lambda 表达式需要注意以下两点:
(1)lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
}
interface GreetingService {
void sayMessage(String message);
}
}
$ javac Java8Tester.java $ java Java8Tester Hello! Runoob
(2)我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter {
void convert(int i);
}
}
(3)ambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1; Converters = (param) -> System.out.println(String.valueOf(param + num)); s.convert(2); num = 5; //报错信息:Local variable num defined in an enclosing scope must be final or effectively final
(4)在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = ""; Comparatorcomparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
Class类的特点:(结合截图理解)
public class Test {
public static void main(String[] args) {
Class clazz = null;
try {
//1、Class.forName()
clazz = Class.forName("venus.Student");
System.out.println("1-->"+clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//2、类名.class
clazz = Student.class;
System.out.println("2-->"+clazz);
//3、对象.getClass()
clazz = new Student().getClass();
System.out.println("3-->"+clazz);
//4、基本数据类型对应的class对象:包装类.TYPE
clazz = Integer.TYPE;
System.out.println("4-->"+clazz);
clazz = Integer.class;
System.out.println("4-->"+clazz);
//5、数组对应的class对象:元素类型[].class
clazz = String[].class;
System.out.println("5-->"+clazz);
//6、某个类的父类所对应的class对象:类名.class.getSuperclass()
clazz = Student.class.getSuperclass();
System.out.println("6-->"+clazz);
}
}
(1)类、枚举、接口、注解、数组类型、原生类型的名称.class
示例:
Class classString=String.class;//类
Class classEnum=RetentionPolicy.class;//枚举
Class classInterface=Serializable.class;//接口
Class classAnnotation=Retention.class;//注解
Class classInt=int.class;//原生类型
Class classIntArray=int[].class;//原生数组类型
Class classStringArray=String[].class;//数组类型
(2)对象.getClass()
由于原生类型不是对象,所以无法使用getClass(),其他类型都是支持的。
示例
Class classString = new String().getClass();// 类
Class classEnum = RetentionPolicy.SOURCE.getClass();// 枚举
Class classInterface = new Serializable() {}.getClass();// 接口
Class classAnnotation = new Documented() {public Class extends Annotation> annotationType() {return null;}}.getClass();// 注解
// Class classInt=。。。;//原生类型不是对象,不能使用getClass()方法
Class classIntArray = new int[] {}.getClass();// 原生数组类型
Class classStringArray = new String[] {}.getClass();// 数组类型
(3)使用Class.forName
Class.forName方法有两个:
1.forName(String name)
2.forName(String name, boolean initialize,ClassLoader loader)
forName(String name)其实调用的是forName(String name,boolean initialize,ClassLoader loader)
forName(className, true, ClassLoader.getCallerClassLoader());
boolean initialize参数很关键,如果为true,类会被初始化,静态变量会赋上初始值,静态代码块会被执行,如果为false则不会被初始化。
Class.forName仍然不支持原生类型(基本类型),但其他类型都是支持的。
Class classString = Class.forName("java.lang.String");// 类
Class classEnum = Class.forName("java.lang.annotation.RetentionPolicy");// 枚举
Class classInterface = Class.forName("java.io.Serializable");// 接口
Class classAnnotation = Class.forName("java.lang.annotation.Documented");// 注解
// Class classInt=。。。;//原生类型不是对象,不能使用Class.forName方法
Class classIntArray = Class.forName("[I");// 原生数组类型
Class classStringArray = Class.forName("[Ljava.lang.String;");// 数组类型
(4)使用ClassLoader.loadClass
此方法也能加载类,效果同Class.forName(className, false,ClassLoader.getCallerClassLoader()),不会初始化类。
但ClassLoader.loadClass跟Class.forName相比,ClassLoader.loadClass不能对数组类型使用。
除了原生类型和数组类型,其他类型都是支持的。
示例:
Class classString = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");// 类
Class classEnum = ClassLoader.getSystemClassLoader().loadClass("java.lang.annotation.RetentionPolicy");// 枚举
Class classInterface = ClassLoader.getSystemClassLoader().loadClass("java.io.Serializable");// 接口
Class classAnnotation = ClassLoader.getSystemClassLoader().loadClass("java.lang.annotation.Documented");// 注解
//Class classInt=。。。;//原生类型不是对象,不能使用ClassLoader.loadClass方法
//Class classIntArray = ClassLoader.getSystemClassLoader().loadClass("[I");// 数组类型不能使用ClassLoader.loadClass方法
//Class classStringArray = ClassLoader.getSystemClassLoader().loadClass("[Ljava.lang.String;");// 数组类型不能使用ClassLoader.loadClass方法
(1)一个java源文件中可以定义多个class。一个java源文件中public的class不是必须的。一个class会定义生成一个xxx.class字节码文件。一个java源文件当中定义公开的类的话,只能有一个并且该类名称必须和java源文件名称一致。
(2)变量在java中必须先定义再赋值才能访问
(3)变量作用域只需记住一句话——除了大括号就不认识了,注意for是一个整体
(4)java语言源代码采用的是Unicode编码方式,标识符可以用中文
(5)成员变量没有手动赋值系统会默认赋值,局部变量不会
(6)方法体中不能再定义方法
(7)java不支持默认参数。java不支持重载运算符。没有参数时形参直接省略,不能用void
(8)this是一个引用,this是一个变量,this变量中保存的内存地址指向了自身,this存储在JVM堆内存java对象内部。
(9)空引用也可以访问带有static的方法,不会产生空指针异常
(10)实例代码块可以编写多个,遵循自上而下的顺序执行。实例代码块在构造方法执行之前执行,构造方法执行一次,实例代码块对应执行一次。
(11)final是一个关键字,表示最终的,不可变的。
final修饰的类无法被继承
final修饰的方法无法被覆盖
final修饰的局部变量一旦赋值之后,不可重新赋值
final修饰的实例变量,因为实例变量有默认值,采用final修饰时必须手动赋值
final修饰引用,一旦指向某个对象后不再断开,不能再指向其他对象。但指向的那个对象的内容可以改变
(12)
public表示公开的,在任何位置都可以访问
private表示私有的,只能在本类中访问
protected同包,子类中可以访问
缺省 同包下可以访问
(13)super不是引用类型,super中存储的不是内存地址,super指向的不是父类对象
super代表的时当前子类对象中的父类型特征
一个构造方法第一行如果没有this(),也没有显式的去调用super(),则系统会默认调用super()
super()的调用只能放在构造方法的第一行,super()和this()不能共存
super()调用了父类中的构造方法,但是并不会创建父类对象
(14)排序:
实现Comparable接口,重写compareTo()方法
实现Comparator接口,重写compare()方法(单独的排序器)
(15)优先队列PriorityQueue的默认大小为11,内部使用Object[]数组来实现。
如果当前容量小于64,则扩容时+2
如果当前容量大于等于64,则扩容时*1.5