因为考研一年多没写代码了,以前学的一些知识也都忘记了,最近实习打算复习一下以前学过的基础知识,以此贴为开始记录。
一、Java常识
-
java三种技术架构
- javaee:主要针对web程序开发;
- javase:主要针对桌面程序开发;
- javame:主要进行手机软件开发以及嵌入式开发;
-
JDK以及JRE
- JDK:java的开发和运行环境,java的开发工具和JRE;
2. JRE: Java程序的运行环境,java运行的类库 + JVM虚拟机;
- JDK:java的开发和运行环境,java的开发工具和JRE;
-
编译运行java文件的命令
-
javac.exe:负责编译.java文件为.class文件;
-
java.exe:启动jvm加载运行时所需要的类库,并且执行class文件;一个class文件执行的入口是main函数;
-
二、Java易错语法基础
这里仅仅指出容易忘记的知识点。
-
标识符命名
- 包含数字,字母以及$和_ ; 注意:
- 数字不可以开头;
- 不可以使用关键字;
- 包含数字,字母以及$和_ ; 注意:
-
八种数据类型以及级别
- char、boolean、byte、short、int、long、float、double;
- 级别从高到低:byte、char、short -> int -> long -> float -> double (注意没有boolean);
- 强制类型转换:当把一个高级别的变量赋值给一个低级别的变量;
-
区别&以及&&的区别(| 和 ||类似)
- &无短路效应;&&具有短路效应;
-
异或符号^
- 两边结果一样则为false;两遍结果不一样则为true;
-
重载
- 重载定义:如果两个名字一致的方法,若他们的参数的个数不同或者类型不同,则称为重载;
- 如何区分重载:当函数同名时,只看参数列表,和返回值类型无关;
-
数组
- 定义数组
String[] arr1 = new String[5]; //方式1 String[] arr2 = {"Hello", "World"}; //方式2
-
内存区域
- Java内存分类:寄存器、本地方法区、方法区、栈、堆;
- 栈:存储的都是局部变量;
- 堆:用于存储对象以及数组(因为Java中数组也是算是对象),也就是实体;
- 实体
- 每一个实体都有内存首地址;
- 堆内存中的实体都有默认初始化值;
- 垃圾回收机制(详情见JVM);
String a = new String("Hellow"); //注意a是存储在栈中的,而a指向的实体是存储在堆中的
三、面向对象
-
特点
- 万物都可以抽象成对象;
- 面向对象将以前的过程中的执行者,变成了指挥者;
- 面向对象这种思想是符合人们思考习惯的一种思想;
-
Java面向对象编程的三大特性
- 封装
- 继承
- 多态
-
重载和重写的区别
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写;
- 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同;
-
Java匿名对象
/** 匿名对象只在占用堆内存 **/ public class Person { public String name; // 姓名 public int age; // 年龄 // 定义构造方法,为属性初始化 public Person(String name, int age) { this.name = name; this.age = age; } // 获取信息的方法 public void tell() { System.out.println("姓名:" + name + ",年龄:" + age); } public static void main(String[] args) { new Person("张三", 30).tell(); // 匿名对象 } }
-
构造代码块
- 构造代码块是什么:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块;
-
静态代码块
- 是什么:就是一个有静态关键字标示的一个代码块区域。定义在类中;
- 作用:可以完成类的初始化。静态代码块随着类的加载而执行,而且只执行一次(new 多个对象就只执行一次)。如果和主函数在同一类中,优先于主函数执行;
-
静态变量和静态方法:
- 什么时候加载:类加载的时候进行加载;
- 非静态变量和方法的加载时机:new 对象的时候进行加载。
-
静态代码块、构造代码块、构造函数同时存在时的执行顺序
- 静态代码块 ———> 构造代码块 ———> 构造函数;
-
接口
- 默认修饰符
- 成员变量 public static final
- 成员方法:public abstract
- 普通类可以实现多个接口,但是只能继承一个父类;
- 接口类可以多继承多个接口;
- 默认修饰符
-
多态在子父类中的成员上的体现的特点
Animal a = new Cat();
- 成员变量:编译和运行时都看左边
- 成员函数:编译看左边,运行看右边
- 静态函数:编译和运行都看左边
-
内部类
- 什么时候使用:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。
- 私有修饰符:通常内部类被封装,都会被私有化,因为封装性不让其他程序直接访问。
- 静态修饰符:如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。注意:如果内部类中定义了静态成员,那么该内部类必须是静态的。
- 内部类编译后的文件名为:“外部类名$内部类名.java”;
- 内部类定义位置:内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。注意:当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。
- 匿名内部类:
- 条件:一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。
- 格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。
-
总结java中的四种权限
范围 public protected default private
同一个类 ok ok ok ok
同一个包 ok ok ok
子类 ok
不同包 ok
四、多线程
-
线程运行的几种状态之间的转换:
被创建:start();
运行:具备执行资格,同事具备执行权;
冻结:sleep(), wait()-------notify()唤醒;线程释放了执行权,同时释放了执行资格;
临时阻塞:线程具备执行资格,但是暂时没有执行权;
消亡:stop();
-
创建线程的两种方法
-
继承Thread,由子类复写run方法
步骤:
1. 定义类继承Thread类;
2. 目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3. 通过创建Thread类的子类对象,创建线程对象;
4. 调用线程的start方法,开启线程,并执行run方法;
-
实现Runnable接口
步骤:
1. 定义类实现Runnable接口。
2. 覆盖接口中的run方法(用于封装线程要运行的代码)。
3. 通过Thread类创建线程对象;
4. 将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5. 调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。
Ticket t = new Ticket();
/*直接创建Ticket对象,并不是创建线程对象。
因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。
所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。*/
Thread t1 = new Thread(t); //创建线程。
只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联
为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。
t1.start();
3. 为什么要有两种方式?
因为实现Runnable接口可以避免单继承的局限性。
-
-
同步
-
优点和缺点
- 优点:解决了线程安全问题;
- 缺点:相对降低心梗,因为判断锁需要消耗资源,可能会发生死锁
-
同步代码块和同步函数的区别
-
同步代码块使用的锁可以是任意对象。同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
-
在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
-
考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。
//为了效率问题,通过双重判断的形式解决。 class Single{ private static Single s = null; private Single(){} public static Single getInstance(){ //锁是谁?字节码文件对象; if(s == null){ synchronized(Single.class){ if(s == null) s = new Single(); } } return s; } }
-
wait和sleep的区别:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
-
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
-
线程的停止
1. 定义循环的结束标记。 2. 如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
-
常用API(java.lang.Thread)
interrupt():中断线程。 setPriority(int newPriority):更改线程的优先级。 getPriority():返回线程的优先级。 toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。 setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。 join:临时加入一个线程的时候可以使用join方法。 当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。 A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行
-
Lock接口
//生产者消费者问题实现 class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; ——count; notFull.signal(); return x; } finally { lock.unlock(); } } }
-
五、IO流
-
分类
-
输入流和输出流;
-
因为处理的数据不同,可以分为字节流和字符流;
-
主要有四种:
- 字节流: InputStream 和 OutputStream
- 字符流: Reader 和 Writer
-
例子
public static void main(String[] args) throws IOException { //读、写都会发生IO异常 /* 1:创建一个字符输出流对象,用于操作文件。该对象一建立,就必须明确数据存储位置,是一个文件。 2:对象产生后,会在堆内存中有一个实体,同时也调用了系统底层资源,在指定的位置创建了一个存储数据的文件。 3:如果指定位置,出现了同名文件,文件会被覆盖。 */ FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException /* 调用Writer类中的write方法写入字符串。字符串并未直接写入到目的地中,而是写入到了流中,(其实是写入到内存缓冲区中)。怎么把数据弄到文件中? */ fw.write("abcde"); fw.flush(); // 刷新缓冲区,将缓冲区中的数据刷到目的地文件中。 fw.close(); // 关闭流,其实关闭的就是java调用的系统底层资源。在关闭前,会先刷新该流。 } //注意 close()和flush()的区别: flush():将缓冲区的数据刷到目的地中后,流可以使用。 close():将缓冲区的数据刷到目的地中后,流就关闭了,该方法主要用于结束调用的底层资源。这个动作一定做。 io异常的处理方式:io一定要写finally。
-
FileReader
import java.io.*; class FileReaderDemo { public static void main(String[] args) throws IOException { /* 创建可以读取文本文件的流对象,FileReader让创建好的流对象和指定的文件相关联。 */ FileReader fr = new FileReader("demo.txt"); int ch = 0; while((ch = fr.read())!= —1) { //条件是没有读到结尾 System.out.println((char)ch); //调用读取流的read方法,读取一个字符。 } fr.close(); } }
//读取数据的第二种方式:第二种方式较为高效,自定义缓冲区。 import java.io.*; class FileReaderDemo2 { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("demo.txt"); //创建读取流对象和指定文件关联。 //因为要使用read(char[])方法,将读取到字符存入数组。所以要创建一个字符数组,一般数组的长度都是1024的整数倍。 char[] buf = new char[1024]; int len = 0; while(( len=fr.read(buf)) != —1) { System.out.println(new String(buf,0,len)); } fr.close(); } }
-
字符流
Reader:用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。 |———BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。 可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。 |———LineNumberReader:跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。 |———InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。 |———FileReader:用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在 FileInputStream 上构造一个 InputStreamReader。 |———CharArrayReader: |———StringReader:
Writer:写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。 |———BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。 |———OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。 |———FileWriter:用来写入字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。 |———PrintWriter: |———CharArrayWriter: |———StringWriter:
-
字节流
InputStream:是表示字节输入流的所有类的超类。 |——— FileInputStream:从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。FileInputStream 用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。 |——— FilterInputStream:包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。 |——— BufferedInputStream:该类实现缓冲的输入流。 |——— Stream: |——— ObjectInputStream: |——— PipedInputStream:
OutputStream:此抽象类是表示输出字节流的所有类的超类。 |——— FileOutputStream:文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。 |——— FilterOutputStream:此类是过滤输出流的所有类的超类。 |——— BufferedOutputStream:该类实现缓冲的输出流。 |——— PrintStream: |——— DataOutputStream: |——— ObjectOutputStream: |——— PipedOutputStream:
-
缓冲区Buffer的使用
BufferedWriter:是给字符输出流提高效率用的,那就意味着,缓冲区对象建立时,必须要先有流对象。明确要提高具体的流对象的效率。 FileWriter fw = new FileWriter("bufdemo.txt"); BufferedWriter bufw = new BufferedWriter(fw);//让缓冲区和指定流相关联。 for(int x=0; x<4; x++){ bufw.write(x+"abc"); bufw.newLine(); //写入一个换行符,这个换行符可以依据平台的不同写入不同的换行符。 bufw.flush();//对缓冲区进行刷新,可以让数据到目的地中。 } bufw.close();//关闭缓冲区,其实就是在关闭具体的流
BufferedReader: FileReader fr = new FileReader("bufdemo.txt"); BufferedReader bufr = new BufferedReader(fr); String line = null; while((line=bufr.readLine())!=null){ //readLine方法返回的时候是不带换行符的。 System.out.println(line); } bufr.close();
//记住,只要一读取键盘录入,就用这句话。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台 String line = null; while((line=bufr.readLine())!=null){ if("over".equals(line)) break; bufw.write(line.toUpperCase());//将输入的字符转成大写字符输出 bufw.newLine(); bufw.flush(); } bufw.close(); bufr.close();
-
流对象
因为功能不同,流的体系中提供N多对象。那么开始的时候,到底该用哪个对象更为合适呢?这就需要明确流的操作规律。
流的操作规律: 1,明确源和目的。 数据源:就是需要读取,可以使用两个体系:InputStream、Reader; 数据汇:就是需要写入,可以使用两个体系:OutputStream、Writer; 2,操作的数据是否是纯文本数据? 如果是:数据源:Reader 数据汇:Writer 如果不是:数据源:InputStream 数据汇:OutputStream 3,虽然确定了一个体系,但是该体系中有太多的对象,到底用哪个呢? 明确操作的数据设备。 数据源对应的设备:硬盘(File),内存(数组),键盘(System.in), 数据汇对应的设备:硬盘(File),内存(数组),控制台(System.out)。 4,需要在基本操作上附加其他功能吗?比如缓冲。 如果需要就进行装饰。
-
转换流
-
原因:转换流可以将字节转成字符,原因在于,将获取到的字节通过查编码表获取到指定对应字符。
-
最强功能: 字节流 + 编码表 。没有转换,没有字符流。
-
FileReader fr = new FileReader("a.txt"); InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk"); 以上两句代码功能一致, 如果仅仅使用平台默认码表,就使用FileReader fr = new FileReader("a.txt"); //因为简化。 如果需要制定码表,必须用转换流。 转换流 = 字节流+编码表。 转换流的子类File = 字节流 + 默认编码表。 凡是操作设备上的文本数据,涉及编码转换,必须使用转换流。
-
六、网络编程
-
java中的IP对象:InetAddress
import java.net.*; class IPDemo{ public static void main(String[] args) throws UnknownHostException{ //通过名称(ip字符串or主机名)来获取一个ip对象。 InetAddress ip = InetAddress.getByName("www.baidu.com");//java.net.UnknownHostException System.out.println("addr:"+ip.getHostAddress()); System.out.println("name:"+ip.getHostName()); } }
-
UDP传输
-
只要是网络传输,必须有socket 。
-
数据一定要封装到数据包中,数据包中包括目的地址、端口、数据等信息。
-
直接操作udp不可能,对于java语言应该将udp封装成对象,易于我们的使用,这个对象就是DatagramSocket. 封装了udp传输协议的socket对象。因为数据包中包含的信息较多,为了操作这些信息方便,也一样会将其封装成对象。这个数据包对象就是:DatagramPacket.通过这个对象中的方法,就可以获取到数据包中的各种信息。DatagramSocket具备发送和接受功能,在进行udp传输时,需要明确一个是发送端,一个是接收端。
-
例子
//发送端 1,建立udp的socket服务,创建对象时如果没有明确端口,系统会自动分配一个未被使用的端口。 2,明确要发送的具体数据。 3,将数据封装成了数据包。 4,用socket服务的send方法将数据包发送出去。 5,关闭资源。 import java.net.*; class UdpSend{ public static void main(String[] args)throws Exception { // 1,建立udp的socket服务。 DatagramSocket ds = new DatagramSocket(8888);//指定发送端口,不指定系统会随机分配。 // 2,明确要发送的具体数据。 String text = "udp传输演示 哥们来了"; byte[] buf = text.getBytes(); // 3,将数据封装成了数据包。 DatagramPacket dp = new DatagramPacket(buf, buf.length,InetAddress.getByName("10.1.31.127"),10000); // 4,用socket服务的send方法将数据包发送出去。 ds.send(dp); // 5,关闭资源。 ds.close(); } }
//接收端 1,创建udp的socket服务,必须要明确一个端口,作用在于,只有发送到这个端口的数据才是这个接收端可以处理的数据。 2,定义数据包,用于存储接收到数据。 3,通过socket服务的接收方法将收到的数据存储到数据包中。 4,通过数据包的方法获取数据包中的具体数据内容,比如ip、端口、数据等等。 5,关闭资源。 class UdpRece { public static void main(String[] args) throws Exception{ // 1,创建udp的socket服务。 DatagramSocket ds = new DatagramSocket(10000); // 2,定义数据包,用于存储接收到数据。先定义字节数组,数据包会把数据存储到字节数组中。 byte[] buf = new byte[1024]; DatagramPacket dp = new DatagramPacket(buf,buf.length); // 3,通过socket服务的接收方法将收到的数据存储到数据包中。 ds.receive(dp);//该方法是阻塞式方法。 // 4,通过数据包的方法获取数据包中的具体数据内容,比如ip,端口,数据等等。 String ip = dp.getAddress().getHostAddress(); int port = dp.getPort(); String text = new String(dp.getData(),0,dp.getLength());//将字节数组中的有效部分转成字符串。 System.out.println(ip+":"+port+"——"+text); // 5,关闭资源。 ds.close(); } }
-
-
TCP传输
-
两个端点的建立连接后会有一个传输数据的通道,这通道称为流,而且是建立在网络基础上的流,称之为socket流。该流中既有读取,也有写入。
-
tcp的两个端点:客户端:对应的对象,Socket;服务端:对应的对象,ServerSocket;
-
例子
//TCP客户端 1,建立tcp的socket服务,最好明确具体的地址和端口。这个对象在创建时,就已经可以对指定ip和端口进行连接(三次握手)。 2,如果连接成功,就意味着通道建立了,socket流就已经产生了。只要获取到socket流中的读取流和写入流即可,只要通过getInputStream和getOutputStream就可以获取两个流对象。 3,关闭资源。 import java.net.*; import java.io.*; //需求:客户端给服务器端发送一个数据。 class TcpClient{ public static void main(String[] args) throws Exception{ Socket s = new Socket("10.1.31.69",10002); OutputStream out = s.getOutputStream();//获取了socket流中的输出流对象。 out.write("tcp演示,哥们又来了!".getBytes()); s.close(); } }
// TCP服务端 1,创建服务端socket服务,并监听一个端口。 2,服务端为了给客户端提供服务,获取客户端的内容,可以通过accept方法获取连接过来的客户端对象。 3,可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。 4,如果通讯结束,关闭资源。注意:要先关客户端,再关服务端。 class TcpServer{ public static void main(String[] args) throws Exception{ ServerSocket ss = new ServerSocket(10002);//建立服务端的socket服务 Socket s = ss.accept();//获取客户端对象 String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+".....connected"); // 可以通过获取到的socket对象中的socket流和具体的客户端进行通讯。 InputStream in = s.getInputStream();//读取客户端的数据,使用客户端对象的socket读取流 byte[] buf = new byte[1024]; int len = in.read(buf); String text = new String(buf,0,len); System.out.println(text); // 如果通讯结束,关闭资源。注意:要先关客户端,在关服务端。 s.close(); ss.close(); } }
七、反射技术
-
反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。大大的增强了程序的扩展性。
-
反射的基本步骤
1. 获得Class对象,就是获取到指定的名称的字节码文件对象。
2. 实例化对象,获得类的属性、方法或构造函数。
- 访问属性、调用方法、调用构造函数创建对象。
-
获取class对象的三种方式
1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。 2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。 前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。 3:使用的Class类中的方法,静态的forName方法。 指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。 // 1. 根据给定的类名来获得 用于类加载 String classname = "cn.itcast.reflect.Person";// 来自配置文件 Class clazz = Class.forName(classname);// 此对象代表Person.class // 2. 如果拿到了对象,不知道是什么类型 用于获得对象的类型 Object obj = new Person(); Class clazz1 = obj.getClass();// 获得对象具体的类型 // 3. 如果是明确地获得某个类的Class对象 主要用于传参 lass clazz2 = Person.class;
-
用法
```java 1)、需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式: Class.forName(classname) 用于做类加载 obj.getClass() 用于获得对象的类型 类名.class 用于获得指定的类型,传参用 2)、反射类的成员方法: Class clazz = Person.class; Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2}); method.invoke(); 3)、反射类的构造函数: Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...}) con.newInstance(params...) 4)、反射类的属性: Field field = clazz.getField(fieldName); field.setAccessible(true); field.setObject(value); ```
- 根据字节码class文件,获取指定类的对象两种方法
1. 调用空参数的构造函数:使用了Class类中的newInstance()方法。 2. 调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。 **综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。** ```java // 如何生成获取到字节码文件对象的实例对象。 Class clazz = Class.forName("cn.itcast.bean.Person");//类加载 // 直接获得指定的类型 clazz = Person.class; // 根据对象获得类型 Object obj = new Person("zhangsan", 19); clazz = obj.getClass(); Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2(); public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。 //获取一个带参数的构造器。 Constructor constructor = clazz.getConstructor(String.class,int.class); //想要对对象进行初始化,使用构造器的方法newInstance(); Object obj = constructor.newInstance("zhagnsan",30); //获取所有构造器。 Constructor[] constructors = clazz.getConstructors();//只包含公共的 constructors = clazz.getDeclaredConstructors();//包含私有的 for(Constructor con : constructors) { System.out.println(con); } } ```
- 获取反射类中的方法
```java //获取类中所有的方法。 public static void method_1() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。 methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。 for(Method method : methods) { System.out.println(method); } } //获取指定方法; public static void method_2() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //获取指定名称的方法。 Method method = clazz.getMethod("show", int.class,String.class); //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。 Object obj = clazz.newInstance(); method.invoke(obj, 39,"hehehe");//执行一个方法 } //想要运行私有方法。 public static void method_3() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); //想要获取私有方法。必须用getDeclearMethod(); Method method = clazz.getDeclaredMethod("method", null); // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。 method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。 } //反射静态方法。 public static void method_4() throws Exception { Class clazz = Class.forName("cn.itcast.bean.Person"); Method method = clazz.getMethod("function",null); method.invoke(null,null); } ```
八、注解
-
什么是注解?
Annotation(注解)就是Java提供了一种为程序元素关联任何信息或任何元数据(metadata)的途径和方法。Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
-
注解出现的位置
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,annotation就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。这些信息被存储在Annotation的“name=value”结构对中。
-
注解的成员提供了程序元素的关联信息(成员称为参数或注解属性)
Annotation的成员在Annotation类型中以无参数的方法的形式被声明。其方法名和返回值定义了该成员的名字和类型。在此有一个特定的默认 语法:允许声明任何Annotation成员的默认值。一个Annotation可以将name=value对作为没有定义默认值的Annotation 成员的值,当然也可以使用name=value对来覆盖其它成员默认值。这一点有些近似类的继承特性,父类的构造函数可以作为子类的默认构造函数,但是也 可以被子类覆盖。
-
注解不会影响程序代码的执行
Annotation能被用来为某个程序元素(类、方法、成员变量等)关联任何的信息。需要注意的是,这里存在着一个基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。另外,尽管一些annotation通过java的反射api方法在运行时被访问,而java语言解释器在工作时忽略了这些annotation。正是由于java虚拟机忽略了Annotation,导致了annotation类型在代码中是“不起作用”的; 只有通过某种配套的工具才会对annotation类型中的信息进行访问和处理
-
注解的作用是什么注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据),常见的作用有以下几种:
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等;
- 在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
- 跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
-
-
JDK自定义注解
-
@Override 表示当前方法覆盖了父类的方法
此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解但没有重写超类方法,则编译器会生成一条错误消息;
-
@Deprecated 表示方法已经过时,方法上有横线,使用时会有警告。
此注释可用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。在使用不被赞成的程序元素或在不被赞成的代码中执行重写时,编译器会发出警告;
-
@SuppressWarings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)
用来抑制编译时的警告信息。与前两个注释有所不同,你需要添加一个参数才能正确使用,这些参数值都是已经定义好了的,我们选择性的使用就好了;
-
-
自定义注解
-
语法规则
- 使用@interface关键字定义注解,注意关键字的位置;
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。 - 成员以无参数无异常的方式声明,注意区别一般类成员变量的声明;
其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称; - 可以使用default为成员指定一个默认值;
- 成员类型是受限的,合法的类型包括原始类型以及String、Class、Annotation、Enumeration (JAVA的基本数据类型有8种:
byte(字节)、short(短整型)、int(整数型)、long(长整型)、float(单精度浮点数类型)、double(双精度浮点数类型)、char(字符类型)、boolean(布尔类型) - 注解类可以没有成员,没有成员的注解称为标识注解,例如JDK注解中的@Override、@Deprecation;
- 如果注解只有一个成员,并且把成员取名为value(),则在使用时可以忽略成员名和赋值号“=” ,例如JDK注解的@SuppviseWarnings ;如果成员名 不为value,则使用时需指明成员名和赋值号"=";
- 使用@interface关键字定义注解,注意关键字的位置;
-
元注解
-
定义:何为元注解?就是注解的注解,就是给你自己定义的注解添加注解,你自己定义了一个注解,但你想要你的注解有什么样的功能,此时就需要用元注解对你的注解进行说明了。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。
-
Java中的四个元注解
-
@Target
@Target说明了Annotation所修饰的对象范围:即注解的作用域,用于说明注解的使用范围(即注解可以用在什么地方,比如类的注解,方法注解,成员变量注解等等)
注意:如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。
取值是在java.lang.annotation.ElementType这个枚举中规定的:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明 -
@Retention
@Retention定义了该Annotation被保留的时间长短:
(1)某些Annotation仅出现在源代码中,而被编译器丢弃;
(2)而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,
(3)而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。
使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
@Retention的取值是在RetentionPoicy这个枚举中规定的
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留) -
@Documented
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
-
@Inherited
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继 承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现, 或者到达类继承结构的顶层。例子:
(1) 定义注解1
(2) 定义注解2
(3)定义一个基类
(4)定义一个子类
(5)通过反射获取子类使用的全部注解
-
-
注解开发实例:通过注解创建数据库的表
-
创建Column注解,标识数据库中的字段信息
-
创建Table注解,标识数据库中的表
-
创建JavaBean
-
创建Main方法,读取JavaBean类中的注解信息,根据注解信息自动生成数据库创建语句
-
-
-
-
-
参考文章
- java封装、继承、多态:https://zhuanlan.zhihu.com/p/450029902
- java基础知识点:https://www.cnblogs.com/schiller-hu/p/10662429.html
- java注解讲解:https://blog.youkuaiyun.com/qq_30347133/article/details/83686068?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-83686068-blog-73824058.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-83686068-blog-73824058.pc_relevant_aa&utm_relevant_index=5