【温故】
1.集合
package review;
import java.util.HashSet;
/*
集合: 存储对象数据 的集合容器。
单例集合
----------| Collection 单例集合 的根接口
----------------| List 如果是实现了List接口的集合类,具备的特点: 有序,可重复。
-------------------| ArrayList ArrayList底层是使用了Object数组实现 的。 特点: 查询速度快,增删慢。
-------------------| LinkedList LinkedList底层是使用了链表数据结构实现的。特点: 查询速度慢,增删快
-------------------| Vector 底层是使用了Object数组实现 的, 实现原理与ArrayList 是一致的,但是是线程安全的,操作效率低。
----------------| Set 如果是实现了Set接口的集合类,具备的特点: 无序,不可重复。
-------------------| HashSet 底层是使用了哈希表实现 的。 特点: 存取速度快。
HashSet存储元素的原理:
往hashSet添加元素的时候,首先会调用元素的hashCode方法得到元素的哈希码值,然后把哈希码值经过运算算出该元素存在哈希表中的位置。
情况1:如果算出的位置目前还没有存在任何的元素,那么该元素可以直接添加到哈希表中。
情况2: 如果算出的位置目前已经存在其他的元素,那么还会调用元素 的equals方法再与这个位置上 的元素比较一次。
如果equals方法返回的是true,那么该元素被视为重复元素,不允许添加。如果equals方法返回 的是false,那么该元素也可以被添加。
------------------| TreeSet 底层是使用了红黑树(二叉树)数据结构实现的。 特点: 对集合中的元素进行排序存储、。
TreeSet要注意的事项:
1. 往TreeSet添加元素 的时候,如果元素具备自然顺序的特点,那么TreeSet会根据元素 的自然顺序特性进行排序 存储。
2. 往TreeSet添加元素 的时候,如果元素不具备自然顺序的特点,那么元素所属的类就必须要实现Comparable接口,把比较的规则定义在CompareTo方法上。
3. 往TreeSet添加元素 的时候,如果元素不具备自然顺序的特点,那么元素所属的类就也没有实现Comparable接口,那么在创建TreeSet对象的时候必须要传入比较器对象。
比较器 的定义格式:
class 类名 implements Comparator{
}
双列集合
----------| Map 存储的数据都是以键值对的形式存在的,键可以不重复,值可重复。
-------------| HashMap 底层也是使用了哈希表实现的。
-------------| TreeMap 底层也是使用了红黑树数据结构实现的。
练习:
*/
class Person{
int id;
String name;
public Person(int id, String name) {
super();
this.id = id;
this.name = name;
}
//复写hashCode()方法和equals()方法,保证元素唯一性
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(Object obj) {
Person p = (Person)obj;
return this.id == p.id;
}
@Override
public String toString() {
return "编号:"+ this.id +" 姓名: "+ this.name;
}
}
public class Demo1_list {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<Person>();
set.add(new Person(110,"狗娃"));
set.add(new Person(110,"狗娃"));
System.out.println("集合的元素:"+ set);
}
}
======================分割线==========================
package review;
/*
1.遍历集合的几种方式
(1)使用迭代器Iterator的方式。
(2)使用增强for循环的方式。
(3)如果有下标,则可以使用下标的方式。
===========================================================================
【遍历数组】
public static void main(String[] args) {
// 遍历数组:
String[] arr = new String[] { "xx", "yy", "zz" };
// 1,增强的for循环
for (String elt : arr) {
System.out.println(elt);
}
// 2,下标的方式
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
===========================================================================
【遍历List】
public static void main(String[] args) {
// 遍历List:
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("cc");
// 1,增强的for循环
for (String elt : list) {
System.out.println(elt);
}
// 2,下标
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 3,迭代器
for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
String elt = iter.next();
System.out.println(elt);
}
}
===========================================================================
【遍历Set】这个没有下标,所以不能用一般for循环遍历
public static void main(String[] args) {
// 遍历Set:
Set<String> set = new HashSet<String>();
set.add("dd");
set.add("ee");
set.add("ff");
// 1,增强的for循环
for (String elt : set) {
System.out.println(elt);
}
// 2,迭代器
for(Iterator<String> iter = set.iterator(); iter.hasNext() ; ){
String elt = iter.next();
System.out.println(elt);
}
}
===========================================================================
【遍历Map】
public static void main(String[] args) {
// 遍历Map:
Map<String, String> map = new HashMap<String, String>();
map.put("aa", "xx");
map.put("bb", "yy");
map.put("cc", "zz");
// 1,增强的for循环(Entry集合)
for (Entry<String, String> entry : map.entrySet()) {
System.out.println(entry);
}
// 2,增强的for循环(Key集合)
for(String key : map.keySet()){
System.out.println(key + " = " + map.get(key));
}
// 3,遍历值的集合
for(String value : map.values()){
System.out.println(value);
}
}
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class Demo2_list {
public static void main(String[] args) {
ArrayList<String> list =new ArrayList<String>();
//使用get方法遍历。
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println("======get方式遍历=========");
for(int i = 0 ; i < list.size() ; i++){
System.out.print(list.get(i)+",");
}
//使用迭代器 注意: 迭代器在迭代的 过程中不能使用集合对象修改集合中的元素个数。如果需要修改要使用迭代器的方法进行修改,
System.out.println("\r\n======迭代器方式遍历=========");
HashSet<String> set = new HashSet<String>();
set.add("狗娃");
set.add("狗剩");
set.add("铁蛋");
/*Iterator<String> it = set.iterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
}
*/
for(String item : set){//增强for循环,在底层也是使用迭代器来完成的
System.out.print(item+",");
}
System.out.println("\r\n======entrySet方式遍历=========");
HashMap<String, String> map = new HashMap<String, String>();
map.put("张三","001");
map.put("李四","002");
map.put("王五","003");
Set<Entry<String,String>> entrys = map.entrySet(); //
for (Entry<String,String> entry : entrys) {
System.out.println("键:"+entry.getKey()+" 值:"+entry.getValue());
}
}
}
======================分割线==========================
2.泛型
package review;
import java.util.ArrayList;
/*
泛型 的好处:
1. 把运行时出现 的问题提前至了编译时。
2. 避免了无谓的强制类型转换。
泛型在集合中的应用(判断下列语法是否正确):
ArrayList<String> list = new ArrayList<String>(); 正确
ArrayList<Object> list = new ArrayList<String>(); 错误
ArrayList<String> list = new ArrayList<Object>(); 错误
考虑到新老系统兼用性:
ArrayList list = new ArrayList<String>(); 正确
ArrayList<String> list = new ArrayList(); 正确
注意: 在泛型中没有多态的概念,两边的数据必须要一致。 或者是只写一边 的泛型类型。
推荐使用: 两边的数据类型都写上一致的。
*/
public class Demo3_genecity {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// list.add(123); //添加了非字符串类型的数据。
//把集合中的所有元素转换成大写。
for(int i = 0 ; i < list.size() ; i++){
String str = list.get(i);
System.out.println(str.toUpperCase());
}
}
}
======================分割线==========================
package review;
/*
自定义泛型: 自定义泛型可以理解为是一个数据类型的占位符,或者是理解为是一个数据类型的变量。
泛型方法:
泛型方法的定义格式:
修饰符 <声明自定义泛型>返回值类型 函数名(形参列表..){
}
注意:
1. 在方法上的自定义泛型的具体数据类型是调用该方法的时候传入实参的时候确定的。
2. 自定义泛型使用的标识符只要符合标识符的命名规则即可。
需求: 定义一个函数可以接收任意类型的参数,要求函数的返回值类型与实参的数据类型要一致。
*/
public class Demo4_genecity {
public static void main(String[] args) {
Integer i= myPrint(12); // Integer
String str = myPrint("abc");
}
//<T> 把T字母声明为自定义 的泛型、
public static <T> T myPrint(T o){//这里T作用只是“占个位置”,表示是一种类型,又因为变量必须先声明再调用,所以在前面加上<T>
//当Integer i = myPrint(12)时候,由于12是Integer类型,所以这个时候T就是Integer类型
return o;
}
}
======================分割线==========================
package review;
import java.util.ArrayList;
import java.util.Comparator;
/*
泛型类(如ArrayList<E>类就是一个应用)
泛型类的定义格式:
class 类名<声明自定义的泛型>{
}
注意的事项:
1. 在类上自定义的泛型的具体数据类型是在创建对象的时候指定的。
2. 在类上自定义了泛型,如果创建该类的对象时没有指定泛型的具体类型,那么默认是Object类型。
*/
class Worker implements Comparator<Worker>{
public int compare(Worker o1, Worker o2) {
// TODO Auto-generated method stub
return 0;
}
}
//自定义 一个集合对象
class MyList<T>{//这里先声明泛型
Object[] arr = new Object[10];//这里不能直接T[] arr = new T[10];因为JVM不知道这个T是什么,所以先用Object来定义
int index = 0;
public MyList(){
}
public void add(T o){//这里自己写一个add方法,然后传入T类型,这时候什么类型调用该方法,那么T就是什么类型
arr[index++] = o;
}
}
public class Demo5_genecity {
public static void main(String[] args) {
MyList<String> list= new MyList<String>();//此时是String类型,那么上面的T就是String类型,如果这里不指定String,那么默认是Object
list.add("123");
MyList list2 = new MyList();
new ArrayList<String>();
}
}
======================分割线==========================
package review;
/*
泛型接口:(如迭代器Iterator<E>就是一个应用)
泛型接口的定义格式:
interface 接口名<声明自定义的泛型>{
}
在接口上自定义泛型要注意的事项:
1. 在接口上自定义泛型的具体数据类型是在实现该接口的时候指定的。
2. 如果一个接口自定义了泛型,在实现该接口的时候没有指定具体的数据类型,那么默认是Object数据类型。
如果想在创建接口实现类对象的时候再指定接口自定义泛型 的具体数据类型?
见如下代码,在new Demo6时候才指定具体数据类型Srting,而不是在实现自定义泛型接口时候就指定。
*/
interface Dao<T>{
public void add(T t);
public void remove(T t);
}
public class Demo6_genecity<T> implements Dao<T>{//第一个T是为了下面复写接口中方法时候需要指定这个T,第二个T是说明这里是需要用到泛型T,只是目前还不明确具体的数据类型。
public static void main(String[] args) {
new Demo6_genecity<String>();
}
public void add(T t) {
// TODO Auto-generated method stub
}
public void remove(T t) {
// TODO Auto-generated method stub
}
}
3.io
package review;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
/*
IO解决问题: 解决设备与设备之间 的数据传输问题。 比如: 硬盘--->内存 内存----->硬盘
字节流:
输入字节流:
---------| InputStream 所有输入字节流的基类。 抽象类。
------------| FileInputStream 读取文件的输入字节流。
------------| BufferedInputStream 缓冲输入字节流。 该类内部其实就是维护了一个8kb字节数组而已。 该类出现的目的是为了提高读取文件数据的效率。
输出字节流:
---------| OutputStream 所有输出字节流的基类。 抽象类。
------------| FileOutputStream 向文件输出数据 的输出字节流。
------------| BufferedOutputStream 缓冲输出字节流。 该类出现的目的是为了提高向文件写数据的效率。 该类内部其实也是维护了一个8kb的字节数组而已。
什么情况使用字节流: 读取到数据不需要经过编码或者解码的情况情况下这时候使用字节流。比如:图片数据
字符流 = 字节流 + 编码(解码)
字符流:
输入字符流
--------| Reader 所有输入字符流的基类。 抽象类。
-----------| FileReader 读取文件字符的输入字符流。
-----------| BufferedReader 缓冲输入字符流。 该类出现的目的是为了提高读取文件字符的效率并且拓展了功能(readLine()),它内部 其实就是维护了一个8192个长度的字符数组。
输出字符流
-------| Writer 所有输出字符流的基类。 抽象类。
------------| FileWriter 向文件输出字符数据的输出字符流。
---------------| BufferedWriter 缓冲输出字符流。该类出现的目的是为了提高写文件字符的效率并且拓展了功能(newLine()),它内部 其实就是维护了一个8192个长度的字符数组。
什么情况下使用字符流:如果读写的都是字符数据,这时候我们就使用字符流。
转换流:
输入字节流的转换流 输入字节流---------输入字符流
InputSrteamReader
输出字节流的转换流
OutputStreamWriter
转换流的作用:
1. 可以把对应的字节流转换成字符流使用。
2. 可以指定码表进行读写文件的数据。
FileReader, FileWriter这两个类默认是使用的是gbk编码 表。不能由你来指定码表读写文件数据。
*/
public class Demo7_io {
public static void main(String[] args) throws Exception {
// testInput();
// writeFile();
readFile();
}
public static void readFile() throws IOException{
/*如果用FileReader流也能读取a.txt中内容,但是读出来的是乱码,因为a.txt是用utf-8编码的,而FileReader(字符流)默
认是GBK码表且不能指定编码表形式,但是转换流可以指定编码表形式(如下面的writeFile()函数)。
*/
//建立文件与程序的输入数据通道
FileInputStream fileInputStream = new FileInputStream("F:\\a.txt");
//创建输入字节流的转换流并且指定码表进行读取
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"utf-8");
int content = 0;
while((content = inputStreamReader.read())!=-1){
System.out.println((char)content);
}
//关闭资源
inputStreamReader.close();
/*FileReader fileReader = new FileReader("F:\\a.txt"); //默认使用的是gbk码表
int content = 0;
while((content = fileReader.read())!=-1){
System.out.print((char)content);
}
//关闭资源
fileReader.close();*/
}
//指定使用utf-8码表把数据写出到文件上。
public static void writeFile() throws IOException{
//建立了文件与程序的数据 通道
FileOutputStream fileOutputStream = new FileOutputStream("F:\\a.txt");
//创建一个输出字节流的转换流并且指定码表进行写数据
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"utf-8");
outputStreamWriter.write("大家好"); //中文在utf-8码表中占三个字节。
//关闭资源
outputStreamWriter.close();
}
public static void testOutput() throws Exception{
Socket socket = new Socket(InetAddress.getLocalHost(),9090);
//获取到socket的输出流对象。
OutputStream outputStream = socket.getOutputStream();
//把输出字节流转换成输出字符流
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
outputStreamWriter.write("不饿!");
}
public static void testInput() throws Exception{
InputStream in = System.in;
// int content = in.read(); // 每次只会读取到一个字节的数据
//需要把字节流转换成字符流使用。
InputStreamReader inputStreamReader = new InputStreamReader(in);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
System.out.println(bufferedReader.readLine());
}
}
======================分割线==========================
4.多线程
package review;
/*
线程:
多线程的好处: 多线程解决了在一个进程中同时可以执行多个任务代码的问题。
自定义线程的创建方式:
方式一:继承Thread.
1. 自定义一个类继承Thread类。
2. 重写Thread的run方法,把自定义线程的任务代码定义在run方法上。
3. 创建Thread子类的对象,并且调用start方法启动一个线程。
方式二: 实现Runnable接口。
1. 自定义一个类实现Runnable接口。
2. 实现Runnable接口中的run方法,把自定义线程的任务代码定义在run方法上。
3. 创建Runable实现类 的对象。
4. 创建Thread对象,并且把Runnable实现类的对象作为参数传递。
5. 调用Thread对象的start方法开启线程。
线程安全 问题的解决方案:
线程安全问题出现 的根本原因:
1. 必须要存在两个或者两个以上的线程共享着一个资源。
2. 操作共享资源的代码必须有两句或者两句以上。
如果将两句合成一句呢,如:
int i = 0;
System.out.println("i="+i++);
这样还是要分至少3步执行:①确定输出语句内部待输出的是什么,②计算i++,③计算"i="+i++
1. 同步代码块
synchronized(锁){
需要被同步的代码
}
2. 同步函数。
修饰符 synchronized 返回值类型 函数名(形参列表..){
}
注意:
1. 同步代码块的锁可以是任意的对象。 同步函数的锁是固定 的,非静态函数的锁对象是this对象。 静态函数的锁对象是class对象。
2. 锁对象必须是多线程共享的对象,否则锁不住。
3. 在同步代码块或者是同步函数中调用sleep方法是不会释放锁对象的,如果是调用了wait方法是会释放锁对象的。
*/
public class Demo8_thread extends Thread {
public Demo8_thread(String name){
super(name);
}
@Override
public void run() {
for(int i = 0 ; i< 100 ; i++){
System.out.println(Thread.currentThread().getName()+":"+ i);
}
}
public static void main(String[] args) {
Demo8_thread d = new Demo8_thread("狗娃");
d.start(); //开启线程。 线程一旦开启就会指定run方法中 的代码。
for(int i = 0 ; i< 100 ; i++){
System.out.println(Thread.currentThread().getName()+":"+ i);
}
}
}
======================分割线==========================
【知新】
1.junit
package junit;
import java.util.Arrays;
import org.junit.Test;
/*
junit(单元测试框架)
比如事先写好一个数组排序的函数,现在需要测试其正确与否,那么之前是要在main方法上调用才可以。
所以目前存在的问题:
1. 目前的方法如果需要测试,都需要在main方法上调用。
2. 目前的结果都需要我们人工对比。
→麻烦。
直接用junit框架完成,先在当前项目上导入junit的jar包,然后在需要测试的方法前面加上@Test即可,如果不想导包,
直接在一个方法之前加入@Test,然后MyEclipse会报错,提示没有junit的jar包,这时候可以自动导入。
然后在需要测试的方法名上单击鼠标右键,选择Run As,选择Junti Test,即可测试。
junit要注意的细节:
1. 如果使用junit测试一个方法的时候,在junit窗口上显示绿条那么代表测试正确,
如果是出现了红条,则代表该方法测试出现了异常不通过。
2. 如果点击方法名、 类名、包名、 工程名运行junit分别测试的是对应的方法,类、 包中 的所有类的test方法,工程中的所有test方法。
(即右键→Run As→Junit Test,那么测试的是当前类或包中所有包含@Test的方法)
3. @Test测试的方法不能是static修饰与不能带有形参。(否则会导致没有任何输出,即看不到任何测试结果)
如果真的要测试一个带有形参的方法怎么办?如public static int getMax(int a,int b){}
那么就在测试类中单独写一个方法去调用需要被测试的所有方法,如:
@Test
public void test1(){
getMax(a,b);
……
}
测试完再将该方法删除即可。
4. 如果测试一个方法的时候需要准备测试的环境或者是清理测试的环境,那么可以@Before、 @After 、@BeforeClass、 @AfterClass这四个注解。
@Before、 @After 是在每个测试方法测试的时候都会调用一次, @BeforeClass、 @AfterClass是在所有的测试方法测试之前与测试之后调用一次而已。
junit使用规范:
1. 一个类如果需要测试,那么该类就应该对应着一个测试类,测试类的命名规范 : 被测试类的类名+ Test.
2. 一个被测试的方法一般对应着一个测试的方法,测试的方法的命名规范是: test+ 被测试的方法的方法名
(如Tool.java文件就是需要被测试的类,那么该类对应一个测试类ToolTest.java,在该测试类内直接调用被测试类
的相关方法即可,其中testGetMax()方法表示测试getMax()方法,然后用junit测试即可)
*/
public class Demo9_junit {
@Test //注解
public void getMax(int a, int b){
int a1 = 3;
int b1 = 5 ;
int max = a1>b1?a1:b1;
System.out.println("最大值:"+max);
}
@Test //哪个方法需要测试,就在哪个方法前面加上@Test即可
public void sort(){
int[] arr = {12,4,1,19};
for(int i = 0 ; i < arr.length-1 ; i++){
for(int j = i+1 ; j<arr.length ; j++){
if(arr[i]>arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("数组的元素:"+Arrays.toString(arr));
}
}
======================分割线==========================
/*
如果现在有如下readFile()方法要测试,即测试读取文件是否成功,需要提前准备被读取的文件,然后测试完还要将
该文件删除,即我们称之为准备测试的环境和清理测试环境,那么需要定义两个方法,如下面程序所示的beforeRead()
和afterRead()两个方法。
我们希望在测试之前先运行beforeRead(),然后在测试结束再运行afterRead(),那么就需要用到注解,这个注解可以
有如下两种选择:
(1)@Before和@After:在测试每个@Test的方法都会运行一次,如本程序中的两个@Test方法:readFile()和sort(),那
直接测试本类时beforeRead()和afterRead()会各运行2次;
(2)@BeforeClass和@AfterClass:自始至终不管多少个@Test方法被测试,都只运行一次,如本程序中管你里面有几个
方法需要被测试,beforeRead()和afterRead()都只运行一次。
需要注意的是:被@BeforeClass和@AfterClass注解的方法必须是静态的,即必须被static修饰。
*/
package junit;
import java.io.FileInputStream;
import java.io.IOException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Demo9_junit2 {
//准备测试的环境
//@Before
@BeforeClass
public static void beforeRead(){
System.out.println("准备测试环境成功...");
}
//读取文件数据,把把文件数据都
@Test
public void readFile() throws IOException{
FileInputStream fileInputStream = new FileInputStream("F:\\a.txt");
int content = fileInputStream.read();
System.out.println("内容:"+content);
}
@Test
public void sort(){
System.out.println("读取文件数据排序..");
}
//清理测试环境的方法
// @After
@AfterClass
public static void afterRead(){
System.out.println("清理测试环境..");
}
}
======================分割线==========================
package junit;
public class Tool {
public static int getMax(){
int a = 3;
int b =5;
int max = a>b?a:b;
return max;
}
public static int getMin(){
int a = 3;
int b = 5;
int min = a<b?a:b;
return min;
}
}
======================分割线==========================
package junit;
import org.junit.Test;
//测试类
public class ToolTest {
@Test
public void testGetMax(){
int max = Tool.getMax();
if(max!=5){
throw new RuntimeException();
}else{
System.out.println("最大值:"+ max);
}
/*
以上代码可用“断言”完成,如下所示:
Assert.assertSame(5, max);
其中expected是期望,actual是真实,即比较expected == actual是否成立,如果成立,说明测试成功,否则测试失败,
抛出异常,那么这个时候就无需再去关注控制台上的内容。
*/
/*
以下结果测试是否通过?
(1)Assert.assertSame(new String("abc"), "abc");
测试失败,因为Assert.assertSame(expected,actual)底层是用==比较,==比较的是两个对象的内存地址,那么这里的
两个对象不同,内存地址也不同,所以测试失败。
(2)Assert.assertEquals(new String("abc"), "abc"); //底层是使用Equals方法比较的
测试成功,因为Assert.assertEquals(expected,actual)底层是用equals方法比较的,equals比较的是两个字符串的内容,
这里当然测试成功。
(3)Assert.assertNull("aa");//这里Assert.assertNull(actual)是判断actual是否为null,是就测试成功,不是就测试失败
测试失败。
(4)Assert.assertTrue(true);//这里Assert.assertTrue(actual)判断actual是否为true,是就测试成功,不是就测试失败
测试成功。
*/
}
@Test
public void testGetMin(){
int min = Tool.getMin();
if(min!=3){
throw new RuntimeException();
}else{
System.out.println("最小值:"+ min);
}
}
}
2.内省
package introspector_BeanUtils;
import java.io.BufferedReader;
/*
需求: 编写一个工厂方法根据配置文件的内容,工厂方法返回对应的对象,并且对象要有对应的属性值。
(配置文件见obj.txt)
*/
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Demo10 {
public static void main(String[] args) throws Exception {
Person p = (Person) getInstance();
System.out.println(p);
}
//根据配置文件的内容生产对象的对象并且要把对象的属性值封装到对象中。
public static Object getInstance() throws Exception{
BufferedReader bufferedReader = new BufferedReader(new FileReader("obj.txt"));
String className = bufferedReader.readLine(); //读取配置文件获取到完整的类名。(因为这时候是不知道配置文件中内容的,你不好直接new对象,所以需要这么做)
Class clazz = Class.forName(className);
//通过class对象获取到无参的构造方法
Constructor constructor = clazz.getConstructor(null);
//创建对象
Object o = constructor.newInstance(null);
//至此,对象已经创建完毕,下面就是要把对象的属性值封装到对象中。
//读取属性值
String line = null;
while((line = bufferedReader.readLine())!=null){
String[] datas = line.split("=");//因为配置文件中一般是“属性名=属性值”的键值对的方式存储,所以这里按照“=”进行切割,得到一个String数组datas
//通过属性名获取到对应的Field对象。
Field field = clazz.getDeclaredField(datas[0]);//这里getDeclaredFiled是为了防止配置文件中的属性信息私有化,这里循环第一次,那么datas[0]是“id=110”中的“id”
if(field.getType()==int.class){//如果当前field的类型是int(当前配置文件obj.txt中就“id=110”和“name=狗娃”两个属性,那么filed的类型要么是int要么是String)
field.set(o, Integer.parseInt(datas[1]));//那么就将它的值设置到对象o当中
}else{//如果当前field的类型不是int,就是String
field.set(o, datas[1]);//那么就将其对应的值设置到对象o当中
}
}
return o;
}
}
//所以这个工厂代码就写完,可以用于产生任意对象,只要配置文件中有信息,就可以产生对应的对象
/*
以后我们开发框架 的时候,我们是经常需要把一些数据封装到对象中的。
*/
======================分割线==========================
package introspector_BeanUtils;
//实体类---javaBean
public class Person {
private int id;
private String name;
public Person(int id, String name) {
super();
this.id = id;
this.name = name;
}
public Person(){}
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;
}
@Override
public String toString() {
return "编号:"+ this.id+" 姓名:"+ this.name;
}
}
======================分割线==========================
package introspector_BeanUtils;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import org.junit.Test;
/*
内省--->一个变态的反射.
内省主要解决 的问题: 把对象的属性数据封装 到对象中。
*/
public class Demo11_introspector {
@Test
public void getAllProperty() throws IntrospectionException{
//Introspector 内省类,通过内省类的特有方法getBeanInfo()获取到指定类的所有属性信息
BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);
//通过BeanInfo获取所有的属性描述七
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); //获取一个类中的所有属性描述器
for(PropertyDescriptor p : descriptors){//获取这个类的所有get方法(遍历到这个类中所有的属性描述器,然后每个描述器用getReadMethod()方法获取到其get方法)
System.out.println(p.getReadMethod());
}
}
@Test
public void testProperty() throws Exception{
Person p = new Person();
//属性描述器(详见API)
PropertyDescriptor descriptor = new PropertyDescriptor("id", Person.class);//第一个参数是配置文件中的配置信息的名字,第二个参数是其所属的class文件
//获取属性对应的get或者是set方法设置或者获取属性了。
/*
这里需要用到PropertyDescriptor类内的方法:
public Method getWriteMethod()获得应该用于写入属性值的方法。
该方法返回应该用于写入属性值的方法。如果无法写入该属性,则可能返回。
*/
Method m = descriptor.getWriteMethod(); //获取属性的set方法。这里是获取Person类的set方法
//执行该方法设置属性值
m.invoke(p,110);//第一个参数是调用该方法的对象,或者说是要设置属性的对象,第二个参数是属性值
Method readMethod = descriptor.getReadMethod(); //是获取属性的get方法。这里是获取Person类的get方法
System.out.println(readMethod.invoke(p, null));
}
}
3.路径问题
package path;
/*
以后的数据库的用户名与密码一般都会 保存到properties(配置文件)
*/
public class Demo12_path {
//硬编码
static String userName = "root";
static String password = "123";
public static void main(String[] args) {
System.out.println("用户名:"+ userName+" 密码:"+password);
}
/*
如果数据库的用户名与密码不保存在配置文件上,而是像上面程序一样写在程序里面,那么如果这时候需要
修改用户名或者密码,那么还要重新打开源代码在里面修改,改完之后再重新编译生成对应的class文件,再
送到服务器上(直接把class文件给服务器即可,不需要给java文件),甚至还要重启服务器→麻烦
所以必须要将数据库的用户名与密码保存到配置文件(properties),那么只要提前写好一个用于读取配置文件信息的
类即可,如DBUtil类,那么后期需要修改信息只要该配置文件即可,别的不用动,更不用重新编译甚至重启服务器。
*/
}
======================分割线==========================
package path;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/*
如果经常会发生变化的数据我们可以定义在配置文件上。 比如说:数据库的用户名与密码。
配置文件的路径应该如何写 呢?
绝对路径:一个文件的完整路径信息。一般绝对路径是包含有盘符的。绝对路径的缺陷: 因为绝对路径是有盘符开头的,有些系统是没有盘符的。
(通用新不好,如在window上的绝对路径在linux就未必能用,因为linux上没有c\d\e\f盘)
相对路径: 相对路径是相对于当前程序的路径。当前路径就是执行java命令的时候,控制台所在的路径。
类文件路径 :类文件路径就是使用了classpath的路径找对应的资源文件。
如果需要使用到类文件路径首先先要获取到一个Class对象。
*/
public class DBUtil {
static Properties properties ;
static{
try {
properties = new Properties();
//去加载配置文件 /
Class clazz = DBUtil.class;
InputStream inputStream = clazz.getResourceAsStream("/db.properties"); // "/"代表了Classpath的路径。 getResourceAsStream 该方法里面使用的路径就是使用了类文件路径。
properties.load(inputStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println("当前路径:"+ new File(".").getAbsolutePath() );
System.out.println("用户名:"+ properties.getProperty("userName")+" 密码:"+properties.getProperty("password"));
}
}