Java核心技术
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JU4Jbwtm-1655624104104)(C:\Users\WadeHao\AppData\Roaming\Typora\typora-user-images\image-20201113172600382.png)]
继承(extends)
-
单根继承原则:Java类只继承一个类,默认情况下都继承Object类。Object类默认有clone,equals,finalize,getClass,hashCode,toString 方法 -
构造函数:
- 每个class都有构造函数
- 不写构造函数,都会有一个空的构造函数
- 每个构造函数的第一句话都是去调用父类空的构造函数,通过super关键字调用。如果第一句是自己的super();则编译器是不会去调用空的构造函数。
-
抽象类 组成:
- 成员变量,个数不限
- 具体方法,方法有实现,个数不限
- 抽象方法,加abstract关键字,个数不限。(具有抽象方法的类是不完整的类)
继承:
- 子类继承抽象类后一定要实现所有的抽象方法,只有实现所有的抽象方法才是完整的类
-
继承的设计技巧:
- 将公共操作和域放在超类
- 不要使用受保护的域
- 使用继承实现 “is-a” 关系
- 除非所有继承的方法都有意义,否则不要使用继承
- 在覆盖方法的时候,不要改变预期的行为
- 使用多态,而不是使用类型信息
- 不要过多的使用反射
实现(implements)
- 接口:如果一个类的所有方法都没有实现,这个类就是接口
- class可以继承一个类,但是可以实现多个接口。extends 必须 放在implements前面
- 接口可以多继承多个接口,没有实现的方法将会叠加
- 接口里面可以定义变量,但是一般是常量
- 抽象类和接口都不能别实例化
接口
-
java四个重要的接口
- Comparable :可比较的
- Clonable :可克隆的
- Runnable :可线程化的
- Serializable:可序列化的
-
接口 回调(callback) 回调:指定某个特定事件发生时该采取的动作。
lambda表达式
写法
(匿名内部类被重写方法的形参列表) -> {
重写方法
}
Lambda表达式的省略写法
- 参数类型可以不写
- 如果只有一个参数,
() 可以省略 - 如果lambda中只有一行,可以省略大括号;在此基础上,如果有返回值可以省略
return
Lambda表达式只能简化函数式接口的匿名内部类的写法形式 函数式接口:
- 必须是接口 并且 接口中只有一个抽象方法
- 通常会加上
@FunctionalInterface 注解
方法引用
- 构造器:
Class::new - 静态方法:
Class::static_method - 特定类的任意对象方法:
Class:method - 特定对象的方法:
instance::method
类转型
-
变量支持相互转化 -
类可以转型,但是只局限于有继承关系的。
-
子类可以转换成父类(向上转型),反之不可以(向下转型) Hunam o1 = new Man();
Man o2 = new Human();
-
向下转型只有一种情况可以:这个父类原本就经历过向上转型 Hunam o1 = new Man();
Man o2 = (Man) o1;
关键字
-
static
-
静态变量: static只依赖于类存在(通过类就可以访问),不依赖实例对象。 -
静态方法:无需对象引用,通过类名就可以直接引用; 在静态方法里面只能使用静态变量或者静态方法。普通方法里面什么都可以调用 -
代码块,static块:只在类的第一次被加载时调用(程序运行期间,这段代码只运行一次)。执行顺序(static块>匿名块>构造方法) static {}
{}
-
单例模式 -
final
- 修饰类:final类不能被继承
- 修饰方法:父类的final方法不可以被重写/改写
- 修饰字段:final变量不能再被赋值。
- 如果变量是基本类型,不能改,只能查
- 如果是对象实例,不能修改指针的值,只能修改指针所指向的值
- 常量设计:
- public static final XXX;
- 常量池:很多基本类行的包装类(Integer等等,Float/Double没有)都有常量池。 相同的值值存储一份,相应的所有变量都指向这一个值,节省内存,共享访问。存放在栈内存区
- 而通过new创建出来的对象,放在堆内存区,不会常量化
- 自动类型 与 包装类型 变量。 会有自动装箱 和 自动拆箱说法。
- 可变对象 与 不可变对象(一旦创建,其属性值不会再改变;所有的属性都是final private,不提供setter方法,类是final或者所有的方法是final,类中包含mutable对象,那么返回拷贝的是深度clone)。
String
-
内容比较 equals 方法 -
是否是同一个对象 == -
不可修改对象,进行加减法操作的时候,效率会很差。 解决方法:StringBuffer/StringBuilder 提供的append方法进行修改。StringBuffer是同步的,线程安全的。StringBuilder是不同步的线程不安全的,修改更快。
jar机制
本质上与c++的DDL文件相似
- jar文件包含多个class文件,只包含class文件
- jar文件压缩后只有一个文件
常用包
数字相关:整数/随机数/浮点数/大数/math工具类
String类:
时间类:
格式化类:
访问权限
- private:只能本类访问
- default:同一个包可以访问(可以修饰类)
- protected:同一个包/子类都可以访问
- public:所有类都可以访问(可以修饰类)
文件处理
windows 是用 \ ,而 linux unix 用的/
\ 可能会造成转义相关的麻烦
-
文件类File
Java.io 包中
理清字节(Byte)/字符/数据类型 概念
文件是以字节保存的,因此程序员将变量保存到文件 需要转化
java处理文件时,只能一次拿一点点数据过来处理。即只能以流的方式处理。
-
节点类 字节:InputStream、OutputStream(FileInputStream,FileOutputStream):分别是(数据从文件到程序、数据从程序到文件) 字符:Reader、Writer(FileReader,FileWriter) tips:以Stream结尾的一般是字节,以er结尾的一般是字符。 -
转化类 InputStreamReader:文件读取字节时,转换为Java能够理解的字符 OutputStreamReader:Java将字符转化为字节并写入到文件中 -
装饰类 DataInputStream、DataOutputStream:封装数据流 BufferedInputStream、BufferedOutputStream:缓存字节流 BufferedReader、BufferedWritter:缓存字符流 因为硬盘读写速度慢,故需要在内存中开放一个缓存区,再慢慢写入到硬盘上,这样做可以把CPU解放出来。
文本文件的读写
-
文本文件的读写
- 写文件
- 先创建文件,写入数据流,关闭文件
- FileOutputStram、OutputStramWriter、BufferedWriter
- BufferedWriter主要的两个方法:
- try-resource语句,自动关闭资源
- 只需关闭最外层的数据流,将会把其上面的所有数据流关闭
例子: public class TestFileWrite {
public static void main(String[] args) {
String filepath = "D:\\Users\\WadeHao\\IdeaProjects\\Learn\\src\\com\\learn\\javaCoreTech\\FileReadWrite\\abc.txt";
writeFile2();
}
public static void writeFile(String filepath) {
FileOutputStream fos = null;
OutputStreamWriter osw = null;
BufferedWriter bw = null;
try {
fos = new FileOutputStream(filepath);
osw = new OutputStreamWriter(fos);
bw = new BufferedWriter(osw);
bw.write("我是");
bw.newLine();
bw.write("韦德壕");
bw.newLine();
} catch (java.io.IOException e) {
System.out.println("文件路径出错,文件没找到");
e.printStackTrace();
} finally {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void writeFile2(String filepath) {
try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filepath)))) {
bw.write("hello world! this is the first line of this txt file.");
bw.newLine();
bw.write("this is the second line of this txt file");
bw.newLine();
} catch (FileNotFoundException e) {
System.out.println("文件路径出错,找不到文件");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
按照BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filepath))) 这个写,文件不存在会自动创建文件。
- 读文件
- 先打开文件,逐行读入数据,关闭文件
- FileInputStream、InputStreamWriter、BufferedReader
- BufferedReader主要方法
- try-resource语句,自动关闭资源
- 关闭最外层的数据流,将会把其上面的所有数据流关闭
public class TestFileRead {
public static void main(String[] args) {
String filepath = "D:\\Users\\WadeHao\\IdeaProjects\\Learn\\src\\com\\learn\\javaCoreTech\\FileReadWrite\\abc.txt";
readFile2(filepath);
}
public static void readFile1(String filepath) {
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
fis = new FileInputStream(filepath);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
String line = "";
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (java.io.IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void readFile2(String filepath) {
String line = "";
try(BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filepath)))) {
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
二进制文件的读写 狭义上说,采用字节编码而非字符编码的文件。 特点:用记事本打开不可读
-
zip文件读写 压缩包:zip rar gz
接口
内部类
为什么需要内部类:
- 内部类方法可以访问该类定义所在的作用域的数据,包括私有数据。(内部类都是私有类)
- 原因是:编译器修改了内部类的构造方法,在内部类的构造方法中添加了一个外部类的对象,使得内部类能够访问到外部类的所有属性 方法 域。
编译器自动的为我们做了这件事情,我们就不需要继续这么做了。而且即使这样做了,也是语法不通过的。 - 所以内部类拥有访问外部类的特权,功能强大。
- 内部类可以对同一个包中的其他类隐藏自己
- 想要定义一个回调函数且不想编写大量代码的时候,使用匿名内部类比较方便
使用内部类访问对象状态
内部类可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。
内部类的特殊语法规则
当需要使用外部类(OuterClass)的内部类(InnerClass)的时候,代码规则如下
OuterClass outerInstance = new OuterClass();
OuterClass.InnerClass innerInstance = outerInstance.new InnerClass();
tip:这说明这种内部类对外部不是完全的隐藏的
内部类是否有用、有必要和安全
java编译器会用 **$**区分内部类名和外部内名的常规文件,而java虚拟机对此一无所知。
eg:将内部类文件翻译为 OuterClassName$InnerClassName.class
局部内部类
当一个内部类只在一个方法或者块里面出现了一次,就把这个内部类的声明写在方法里面,形成局部内部了。
局部内部类不能用public或private关键字。它的作用域被限定在声明这个局部类的块中。
对外部是完全隐藏的(优势)
public void start() {
class Day{}
}
{
class Year{}
}
注意:由外部方法访问变量
局部内部类可以访问包含它的外部类以及局部变量(这个局部变量只能是被final关键字修饰的)
匿名内部类
在局部内部类的基础上再前进一步,当只创建一个这个类的对象的时候,就不用命名了。 这个类就是匿名内部类。
语法格式为:
new interfaceType(construction paraments){
}
多年来,Java 程序员习惯的做法是用匿名内部类实现事件监听器和其他回调。如今最好 还是使用 lambda 表达式
静态内部类
使用内部类的时候,需要把一个类隐藏在另外一个类的内部,并不需要内部类引用外部类对象。 则需要用关键字static修饰内部类,使之为静态内部类。
-
只有内部类可以用 static 声明 -
静态内部类没有引用外部类的权限,其他的跟内部类一样 -
当需要用到的一个类很有可能跟Java库里的其他类重名,为了避免重名,可以使用静态内部类,因为它对其他类来说是完全隐藏的 package com.company.innerClass;
public class StaticInnerClassTest {
public static void main(String[] args) {
double[] values = new double[20];
for (int i = 0; i < values.length; i++) {
values[i] = 100 * Math.random();
}
ArrayAlg.Pair minmax = ArrayAlg.minmax(values);
System.out.println("min = " + minmax.getFirst());
System.out.println("max = " + minmax.getLast());
}
}
class ArrayAlg {
// 静态方法
public static Pair minmax(double[] values) {
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
for (double value : values) {
if (min > value)
min = value;
if (max < value)
max = value;
}
return new Pair(min, max);
}
// 静态内部类 这个类可能跟其他类容易重名
public static class Pair {
private double first;
private double last;
public Pair(double first, double last) {
this.first = first;
this.last = last;
}
public double getFirst() {
return first;
}
public double getLast() {
return last;
}
}
}
代理(proxy)
异常 断言 日志 调试
异常
字类抛出的异常不能是父类异常的父类。 子类抛出父类抛出的所有的异常。
自定义异常:
- 继承Exception 或者 RuntimeException
- 重点在构造函数
- 采用throw抛出异常
断言
在以往的程序设计中,要判断一个值是否合法会用到很多的 if 判断语句 或者 try-catch 捕获异常。但是这样的代码会一直存在于整个程序之中。
断言机制允许这样的检测语句会被自动的移走。
启用/禁用断言
- 默认情况下是禁用的,通过
java -enableassert MyApp 或者 java -ea MyApp 启用
断言发生错误是致命的 不看了
泛型程序设计
泛型类
public class Pair<T> {
private T first;
private T second;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
}
泛型方法
泛型方法:可以定义在普通类中也可以定义在泛型方法
public static <T> T getMiddle(T... a) {
return a[a.length/2];
}
publis stactic <T extends Comparable> T min(T[] arrr) {
}
调用的时候用
String middle = A.getMiddle("ss", "A", "sdasdasd", "88");
tips:传入可变参数的函数,最常用的即使printf() 函数。
类型变量的限定
要确定使得T所属的类有comparaTo方法,则只需要让这个类实现Comparable接口。
<T extends Comparable>
使用关键字extrends而不是implements:T表示的是绑定类型的子类型。
绑定类型可以是类,也可以是接口。
public static <T extends Comparable> T min(T[] a) {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for ( int i = 0; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0)
smallest = a[i];
}
return smallest;
}
-
泛型代码 和 虚拟机 JVM中没有泛型类。原始类型用第一个限定的类型变量来替换,如果没有就用Object替换。 虚拟机中没有泛型。只有普通的方法和类。 -
通配符类型
-
概念 允许类型参数变化。 Pair<? extends Manager>
表示任何泛型Pair类型,它的类型参数是Manager的子类 -
通配符的超类型限定 Pair<? super Manager>
类型参数限制为Manger的超类 这种情况返回的类型就无法得到保证,编译器会将它变为 Object 类型 -
一个子类型可以有多个绑定类型,用 & 分隔 T extends Comparable & Cloneable
-
无限定通配符 —— “?” Pair<?>
不能在类创建的时候用,在方法里面用做形参。 -
通配符捕获 通配符不是类型变量,因此不能在编写代码中使用 “?” 作为一种类型 example:编写一个交换Pair元素的方法
public static void swap(Pair<?> p) { swapHelper(p);}
public static <T> void swapHelper(Pair<T> p) {
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
-
约束 和 局限性
-
不能用基本数据类型实例化泛型参数 -
泛型不能用于类型查询(instanceof) if (object instanceof Pair<String>)
-
getClass 方法总是返回原始类型 Pair<String> stringPair;
Pair<User> userPair;
sout(stringPair.getClass() == userPair.getClass());
-
不能常见参数化类型的数组(就是不能创建泛型数组) Pair<String>[] table = new Pair<String>[];
要想实现相同的功能,需要用 其他 集合来封装 List<Pair<String>> ...
-
不能实例化类型变量 new T();
new T[];
T.class;
-
不能在静态域 或 静态方法中引用泛型变量 class<T> {
private static T singleInstance;
private static T getInstance() {...}
}
-
不能抛出 捕获泛型类对象。 实际上 泛型类扩展Throwable接口是不合法的! 除非是这样的写法 class Pair<T> {
public static <T extends Throwable> void doWork(T t) throws T {
try {
} catch (Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
}
-
擦除后 可能会有 冲突
boolean equals(String);
boolean equals(Object);
枚举类
如何定义
public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE;}
这个类具有四个实例,再次尽量不需要构造新对象。
在比较两个枚举类型的值时,永远不要调用eqials,而是直接用 “==” 就可以
Enum类
所有的枚举类都是Enum的子类,继承了 toString()``````valueOf``````values 返回一个包含枚举所有对象的数组 等方法。
可以在枚举类中添加一些构造器、方法、域。
构造器只有在构造枚举常量的时候会被调用。
public enum Size {
SMALL("S"),
MEDIUM("M"),
LARGE("L"),
EXTRA_LARGE("xl");
private String abbreviation;
private Size(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getAbbreviation() {
return abbreviation;
}
}
面向对象编程 和 面向接口编程
类是具有相同属性和服务的一组对象的集合。类只是为所有的对象定义了抽象的属性与行为。
接口是一组规则的集合,它规定了实现本接口的类或者继承本接口必须拥有的一组规则。
接口是在一定粒度视图上同类事物的抽象表示。其中“同类事物”的概念是抽象的。
-
面向对象 -
面向接口
-
定义:在系统分析和架构中,分清层次依赖关系,每个层次不是直接向上层提供服务(不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,不是依赖具体类。 -
优点:
- 下层发生改变时,只要接口与接口的功能不变,上层就不需要做任何修改。
注解
基本作用:检测和约束
内置注解(常见三个)
@Override
重写方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated
表示不推荐使用的方法
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SupressWarnings
镇压警告的作用,可以用在类 和 方法上面
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
元注解
负责解释其他注解的注解
@Target()
描述注解的使用范围,注解在那些地方可以使用
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
其他注解大同小异
@Retention()
表示注解在什么地方有效(Source<Class<Runtime)
@Document
表示讲注解生成在javadoc中
@Inherited
子类继承父类的注解
自定义注解
使用@interface定义
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值为 Class,String,Enum)
- 可以通过default设置 默认值(一般设置为 “” , Void.class)
- 如果只有一个参数,一般命名为value
- 注解元素必须要有值,定义注解时,经常使用空字符串、0作为默认值
使用注解
- 根据注解确定在哪里可以使用
- 注解的元素没有默认值,就必须赋值
反射
动态语音:在程序运行的时候改变程序的结构。eg:将一个字符串转换成程序表达式 等等。
能够分析类能力的程序称之为反射。 或者 通过对象获得一个类
类加载完之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。有了这个类,就可以看到类的结构,像镜子一样;故称之为反射。
概述
反射机制可以用来:
- 在运行中分析类
- 在运行中查看对象,eg:编写一个toString方法供所有的类使用
- 实现通用的数组操作代码
- 利用Method对象
Class类
-
在程序运行期间,JVM为所有的对象维护一个被称为运行时的类型标识/信息。保存这些信息被称为Class -
一个Class对象表示一个特定类的属性。最常用的方法就是getName,返回类的名字。 -
Object中的getClass方法会返回一个Class类型的实例 -
newInstance 可以快速的创建一个类的实例:实例.getClass().newInstance() ,这个方法默认使用类中的无参构造函数。没有的话会抛出异常,否则需要使用Constructor里面的newInstance方法 -
Apple.class 返回一个Class<Apple> 的实例 -
可以通过静态方法**Class.forName()**获取类名所得的对象。参数必须是类名 或者 接口名,否则抛出异常。 XXX.class 与 Class.forName() 的区别:Class.forName(“XXXX”);的作用是要求JVM查找并加载指定的类,如果在类中有静态初始化的话,JVM必然会执行该类的静态代码段,这时是不确定该类是否已经在JVM中加载。而XXXX.class是在JVM中加载已经加载过的类。
利用反射分析类的能力
java.lang.reflect; 包下面有三个类 Field ,Method ,Constructor ,分别对应着类的域,方法和构造器。
-
三者共有的方法: 1.1. getName() 用来返回项目的名称 1.2. getModifiers :返回一个整型数值,用不同的位开关描述public 和 static 等修饰符使用状况。 -
Field 2.1. getType:用来返回描述域所属类型的Class对象 2.2. -
Method 3.1. -
Constructor -
Modifier 5.1. isPublc isPrivate isFinal 方法可以判断构造器和方法是否是 public、private、final -
Class 6.1. getFields getMethods getConstructor 方法分别返回类提供的public域、方法和构造器数组 6.2. geDeclaredtFields getDeclaredMethods getDeclaredConstructor 返回类声明的全部域、方法和构造器数组
在运行时利用反射分析对象
反射机制的默认行为受限于Java的访问控制。想要访问就必须使用 Field、Method、和Constructor 提供的f.setAccessinle(true) 方法覆盖原来的访问控制。这个方法是从AccessibleObject类中继承过来的。
通过反射创建对象
通过 newInstance() 方法:
- 通过实例对象调用
- 通过构造器调用创建
public class UserTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c = Class.forName("com.company.javaCoreTech.reflection.User");
User u1 = (User) c.newInstance();
System.out.println(u1);
Constructor<?> constructor = c.getDeclaredConstructor(int.class, String.class, String.class);
User u2 = (User) constructor.newInstance(01, "admin", "123456");
System.out.println(u2);
}
}
User{id=0, username='null', password='null'}
User{id=1, username='admin', password='123456'}
通过反射调用普通方法
先用getMethod() 获取方法,然后用method.invoke(obj,params...) 调用这个方法
public class UserTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class c1 = Class.forName("com.company.javaCoreTech.reflection.User"); User u2 = (User) constructor.newInstance(01, "admin", "123456");
Method hello = c.getDeclaredMethod("hello", String.class);
hello.invoke(u2, "我的用户名是");
}
}
获取泛型信息
只有在 Method 和 Constructor 里面有 genericParameterXXX 等方法
public class ReflectionGenericTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class<?> c = Class.forName("com.company.javaCoreTech.reflection.Apple");
Method setK = c.getDeclaredMethod("setK", Object.class);
Type[] genericParameterTypes = setK.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
}
}
}
class Apple<K, V> {
private K k;
private V v;
private String name;
public Apple(K k, V v, String name) {
this.k = k;
this.v = v;
this.name = name;
}
@Override
public String toString() {
return "Apple{" +
"k=" + k +
", v=" + v +
", name='" + name + '\'' +
'}';
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
获取注解信息
在于 getAnnotation() 方法
public class ReflectionAnnotationTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c = Class.forName("com.company.javaCoreTech.reflection.Student");
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
TableWade wade = c.getAnnotation(TableWade.class);
System.out.println(wade.value());
Field name = c.getDeclaredField("name");
FiledWade annotation = name.getAnnotation(FiledWade.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@TableWade("wade")
class Student{
@FiledWade(columnName = "id", type = "id", length = 10)
private int id;
@FiledWade(columnName = "name", type = "varchar", length = 10)
private String name;
@FiledWade(columnName = "pw", type = "varchar", length = 10)
private String pw;
public Student() {}
public Student(int id, String name, String pw) {
this.id = id;
this.name = name;
this.pw = pw;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", pw='" + pw + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableWade {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledWade {
String columnName();
String type();
int length();
}
类加载
Java 内存
线程共有
方法区
方法区也是整个Java应用程序共享的区域,它用于存储所有的类信息、常量、静态变量、动态编译缓存等数据,可以大致分为两个部分,一个是类信息表,一个是运行时常量池。方法区也是我们要重点介绍的部分。
堆
堆是整个Java应用程序共享的区域,也是整个虚拟机最大的一块内存空间,而此区域的职责就是存放和管理对象和数组,而我们马上要提到的垃圾回收机制也是主要作用于这一部分内存区域。
线程独有
程序技术器
首先我们来介绍一下程序计数器,它和我们的传统8086 CPU中PC寄存器的工作差不多,因为JVM虚拟机目的就是实现物理机那样的程序执行。在8086 CPU中,PC作为程序计数器,负责储存内存地址,该地址指向下一条即将执行的指令,每解释执行完一条指令,PC寄存器的值就会自动被更新为下一条指令的地址,进入下一个指令周期时,就会根据当前地址所指向的指令,进行执行。
而JVM中的程序计数器可以看做是当前线程所执行字节码的行号指示器,而行号正好就指的是某一条指令,字节码解释器在工作时也会改变这个值,来指定下一条即将执行的指令。
因为Java的多线程也是依靠时间片轮转算法进行的,因此一个CPU同一时间也只会处理一个线程,当某个线程的时间片消耗完成后,会自动切换到下一个线程继续执行,而当前线程的执行位置会被保存到当前线程的程序计数器中,当下次轮转到此线程时,又继续根据之前的执行位置继续向下执行。
程序计数器因为只需要记录很少的信息,所以只占用很少一部分内存。
虚拟机栈
虚拟机栈就是一个非常关键的部分,看名字就知道它是一个栈结构,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(其实就是栈里面的一个元素),栈帧中包括了当前方法的一些信息,比如局部变量表、操作数栈、动态链接、方法出口等。
局部变量表在class文件中就已经定义好了,操作数栈就是我们之前字节码执行时使用到的栈结构; 每个栈帧还保存了一个可以指向当前方法所在类的运行时常量池,目的是:当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,然后就能直接调用对应方法,这就是动态链接(我们还没讲到常量池,暂时记住即可,建议之后再回顾一下),最后是方法出口,也就是方法该如何结束,是抛出异常还是正常返回。
本地方法栈
本地方法栈与虚拟机栈作用差不多,但是它作用于本地方法,这里不多做介绍。
堆
- 存放new的对象和数组
- 可以被所有线程共享,不会存放别的对象引用
栈
- 存放基本数据类型(包括这个基本数据类型的具体指)
- 引用对象的变量(包括这个引用在堆里的具体位置)
方法区
- 可以被所有的线程共享
- 包含了所有的class 和 static 变量
类的加载过程
如果类还未加载的内存中,她将经历三个步骤
- 类的加载(Load):将类的class文件读入内存,并为之创建一个java.lang.Class对象。这个过程由类加载器完成。
- 类的链接(Link):将类的二进制数据合并到 JRE 中(java runtime environment)
- 验证:确保加载的信息是符合 jvm 规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 类的初始化(Initialize):JVM负责堆类进行初始化
- 执行类构造器
<clinit>() 方法的过程。<clinit>() 方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。 类构造器是构造类的信息 - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的
<clinit>() 方法在多线程环境中被正确的加锁和同步
类的初始化
类的主动引用(初始化)
- 虚拟机启动时,先初始化由main方法的类
- new 一个类的对象 的那个类
- 调用静态成员(除了final的)和静态方法
- 使用java.lang.reflect包方法对类进行反射调用
- 当类的初始化时,父类未初始化,会先初始化其父类
类的被动引用(未初始化)
- 访问一个静态域时,只有真正申明这个域的类才会被初始化。eg:通过子类调用父类的静态域的时候,不会导致子类的初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用厂里不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
类加载器的作用
作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时的数据结构。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。 jvm 垃圾回收机制可以回收这些Class对象。
类加载器的类型
引导类加载器
扩展类加载器
系统类加载器
|