接口,泛型
day25
Map接口
一、Map的实现类的结构:
|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
面试题:
- HashMap的底层实现原理?
- HashMap 和 Hashtable的异同?
- CurrentHashMap 与 Hashtable的异同?(暂时不讲)
二、Map结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
三、HashMap的底层实现原理?以jdk7为例说明:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put... ---> map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置上的数据为空,此时的key1-value1添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)方法,比较:
如果equals()返回false:此时key1-value1添加成功。----情况3
如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8 相较于jdk7在底层实现方面的不同:
- new HashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是:Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
4.1 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
4.2 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量 * 填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
四、LinkedHashMap的底层实现原理(了解)
源码中:
`static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after;//能够记录添加的元素的先后顺序
Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
五、Map中定义的方法:
添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合
①遍历所有的key集
`//遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } `
②遍历所有的value集
`//遍历所有的value集: values()
Collection values = map.values();
for(Object obj : values){ System.out.println(obj);
} `
③遍历所有的key-value
`//遍历所有的key-value
//方式一:entrySet()
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator(); while(iterator1.hasNext()){
Object obj = iterator1.next();
//entrySet集合中的元素都是entry
Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey() + "------>" + entry.getValue());
}
System.out.println();
//方式二:
Set keySet = map.keySet();
Iterator iterator2 = keySet.iterator(); while(iterator2.hasNext()){
Object key = iterator2.next();
Object value = map.get(key); System.out.println(key + "======" + value); } `
总结:常用方法:
添加:put(Object key,Object value)
删除:remove(Object key)
修改:put(Object key,Object value)
查询:get(Object key)
长度:size()
遍历 :keySet() / values() / entrySet()
TreeMap
向TreeMap中添加key-value,要求key必须是由同一个类创建的对象
因为要按照key进行排序:自然排序 、定制排序
TreeSet排序例子
`import org.junit.Test;
import java.util.*; public class TreeMapTest {
//自然排序
@Test
public void test1(){
TreeMap map = new TreeMap();
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
//定制排序
@Test
public void test2(){
TreeMap map = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge()); } throw new RuntimeException("输入的类型不匹配!"); } });
User u1 = new User("Tom",23);
User u2 = new User("Jerry",32);
User u3 = new User("Jack",20);
User u4 = new User("Rose",18);
map.put(u1,98);
map.put(u2,89);
map.put(u3,76);
map.put(u4,100);
Set entrySet = map.entrySet(); Iterator iterator1 = entrySet.iterator(); while (iterator1.hasNext()){
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "---->" + entry.getValue());
}
}
}
Collections:操作Collection、Map的工具类
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
其中要注意copy函数的使用:
`//报异常:IndexOutOfBoundsException("Source does not fit in dest")
List dest = new ArrayList(); Collections.copy(dest,list); `
正确的用法:
`List dest = Arrays.asList(new Object[list.size()]);
System.out.println(dest.size());
//list.size();
Collections.copy(dest,list); System.out.println(dest);
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
`//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list); `
Properties
Properties:常用来处理配置文件。key和value都是String类型
例子
`import java.io.FileInputStream;
import java.io.IOException; import java.util.Properties;
public class PropertiesTest {
//Properties:常用来处理配置文件。key和value都是String类型
public static void main(String[] args) { FileInputStream fis = null;
try {
Properties pros = new Properties();
fis = new FileInputStream("jdbc.properties"); pros.load(fis);//加载流对应的文件
String name = pros.getProperty("name"); String password =pros.getProperty("password"); System.out.println("name = " + name + ", password = " + password); } catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
day26
泛型的使用
1.jdk 5.0新增的特性
2.在集合中使用泛型:
以ArrayList为例:
`@Test
public void test1(){
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
//list.add("Tom");
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){ System.out.println(iterator.next());
} } `
以HashSet为例
`@Test
public void test2(){
Map<String, Integer>map = new HashMap<>(); map.put("Tom",87); map.put("Jerry",87); map.put("Jack",67);
//map.put(123, "kkk");
//泛型的嵌套
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>>iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next(); String key = e.getKey();
Integer value = e.getValue(); System.out.println(key + "----->" + value);
}
} `
总结:
① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
② 在实例化集合类时,可以指明具体的泛型类型
③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。
比如:add(E e) --->实例化以后:add(Integer e)
④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换
⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。
3.如何自定义泛型结构:泛型类、泛型接口;泛型方法
如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型,如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
定义一个继承了Order的子类,它的类型为
`public class SubOrder extends Order<Integer> { public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>(); for (E e: arr){
list.add(e);
} return list; } } `
那么,由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
`SubOrder sub1 = new SubOrder(); sub1.setOrderT(1122); `
如果一个子类在继承时未声明类型:
`public class SubOrder1<T> extends Order<T> { } `
声明时:
`SubOrder1<String> sub2 = new SubOrder1<>(); sub2.setOrderT("order2......"); `
泛型不同的引用不能相互赋值。
`ArrayList<String> list1 = null; ArrayList<Integer> list2 = new ArrayList<>();
//泛型不同的引用不能相互赋值。
//list1 = list2; `
说明:
\1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:< E1,E2,E3>
\2. 泛型类的构造器如下 public GenericClass(){}
。而下面是错误的public GenericClass<E>(){}
3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
\4. 泛型不同的引用不能相互赋值。尽管在编译时 ArrayList 和 ArrayList 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中。
\5. 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于Object。经验: 泛型要使用一路都用。要不用,一路都不要用。
\6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象 。
\7. jdk1.7,泛型的简化 操作 ArrayList<Fruit> flist = new ArrayList<>();
\8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
\9. 在类接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
\10. 异常类不能是泛型的
\11. 不能使用 new E[]
。但是可以 E[] elements = (E[])new Object[capacity];
参考:ArrayList 源码中声明: Object[] elementData
而非泛型参数类型数组。
\12. 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
没有类型 擦除
具体类型
子类保留父类的泛型:泛型子类
全部保留
部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型
泛型方法
泛型方法的格式: [访问权限] <泛型> 返回类型 方法名 ([泛型标识 参数名称]) 抛出的异常
泛型在继承方面的体现
虽然类A是类B的父类,但是G和G二者不具备子父类关系,二者是并列关系。
补充:类A是类B的父类,A
通配符的使用
通配符:?
类A是类B的父类,G和G是没有关系的,二者共同的父类是:G<?>
`@Test
public void test1(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
System.out.println(list1);
System.out.println(list2);
List<String> list3 = new ArrayList<>();
list3.add("ASA");
list3.add("DGA");
list3.add("Gesag");
//添加:对于List<?>就不能向其内部添加数据
//list.add("AA");
//list.add('?');
//除了null例外;
list.add(null);
//获取(读取):允许读取数据,读取的数据类型为Object。
Object o = list.get(0);
System.out.println(o); `
分析:
因为?是两者的共同父类,所以可以将list1和2传递给list
添加(写入):对于List<?>就不能向其内部添加数据。除了null
读取:允许读取数据,读取的数据类型为Object。
有限制条件的通配符的使用。
? extends A:类型通配符上限可以理解为<=
G<? extends A> 可以作为G和G的父类,其中B是A的子类
? super A:类型通配符下限可以理解为>=
G<? super A> 可以作为G和G的父类,其中B是A的父类
`@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>(); List<Person> list4 = new ArrayList<Person>(); List<Object> list5 = new ArrayList<Object>(); list1 = list3;
list1 = list4;
//list1 = list5;
//list2 = list3;
list2 = list4;
list2 = list5;
//读取数据:
list1 = list3;
Person p = list1.get(0);
//编译不通过
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
////编译不通过
//Person obj = list2.get(0);
//写入数据:
//编译不通过
//list1.add(new Student());
//编译通过
list2.add(new Person());
list2.add(new Student()); } `
day27
File类的使用
- File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
- File类声明在java.io包下
- File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
- 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
如何创建File类的实例
`File(String filePath) `
`File(String parentPath,String childPath) `
`File(File parentFile,String childPath) `
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
路径分隔符
windows:\\ unix:/ */
public String getAbsolutePath():获取绝对路径
public String getPath() :获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
public boolean renameTo(File dest):把文件重命名为指定的文件路径
比如:file1.renameTo(file2)为例:
要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏
创建硬盘中对应的文件或文件目录
public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
删除磁盘中的文件或文件目录
public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
要想删除成功,io4文件目录下不能有子目录或文件
流
一、流的分类:
1.操作数据单位:字节流8bit、字符流16bit
2.数据的流向:输入流、输出流
3.流的角色:节点流、处理流
抽象基类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
二、流的体系结构
抽象基类 | 节点流(或文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream | FileInputStream(read(byte[] buffer)) | BufferedInputStream (read(byte[] buffer)) |
OutputStream | FileOutputStream(write(byte[] buffer,0,len) | BufferedOutputStream (write(byte[] buffer,0,len) / flush() |
Reader | FileReader (read(char[] cbuf)) | BufferedReader (read(char[] cbuf) / readLine()) |
Writer | FileWriter (write(char[] cbuf,0,len) | BufferedWriter (write(char[] cbuf,0,len) / flush() |
说明点:
\1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
\2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
\3. 读入的文件一定要存在,否则就会报FileNotFoundException。
学会对read()操作升级:使用read的重载方法
示例代码:
`@Test public void testFileReader1(){ FileReader fr = null;
try{
//1.File类的实例化
File file = new File("hello.txt");
//2.FileReader流的实例化
fr = new FileReader(file);
//3. 读入的操作
//read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
char[] cbuf = new char[5];
int len;
while((len = fr.read(cbuf)) != -1){
//方法一:
for(int i = 0;i < len;i++){
System.out.println(cbuf[i]);
}
//方法二:
// String str = new String(cbuf, 0 ,len);
// System.out.println(str);
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(fr != null){
try {
//流的关闭操作
fr.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
} `
读取文件的推荐方式:
`while((len = fr.read(cbuf)) != -1){
String str = new String(cbuf, 0 ,len); System.out.println(str);
} `
从内存中写出数据到硬盘的文件里。
说明:
\1. 输出操作,对应的File可以不存在的。并不会报异常
\2. File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
File对应的硬盘中的文件如果存在:
如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):对原有文件的覆盖
如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
IO流基本操作:
1.File类的实例化
2.FileReader流的实例化
3.读入的操作
4.资源的关闭
read():返回读入的一个字符。如果达到文件末尾,返回-1
不能使用字符流来处理图片等字节数据
测试FileInputStream和FileOutputStream的使用
- 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
使用字节流FileInputStream处理文本文件,可能出现乱码。
图片复制操作示例代码
`/* 实现对图片的复制操作 */
@Test
public void testFileInputOutputStream() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// File类的实例化
File srcFile = new File("爱情与友情.jpg");
File destFile = new File("爱情与友情2.jpg");
// FileReader流的实例化
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
//复制的过程
byte[] buffer = new byte[5];
int len;
while((len = fis.read(buffer)) != -1){
fos.write(buffer,0,len);
} } catch (IOException e) {
e.printStackTrace();
} finally {
// //流的关闭操作
if(fos != null){
try {
fos.close();
} catch (IOException e) { e.printStackTrace();
} }
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
} } } `
处理流之一:缓冲流的使用
1.缓冲流:
-
BufferedInputStream
-
BufferedOutputStream 有flush()刷新
-
BufferedReader
-
BufferedWriter 有flush()刷新
2.作用:提供流的读取、写入的速度
提高读写速度的原因:内部提供了一个缓冲区
3.处理流,就是“套接”在已有的流的基础上。
说明:
① 关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
② 使用String\char[]读取数据:
`//方式二:使用String
String data;
while((data = br.readLine()) != null){
//方法一:
bw.write(data + "\n");//data中不包含换行符
//方法二:
bw.write(data);//data中不包含换行符
bw.newLine();//提供换行的操作
} `
复制文件的示例代码:
`//实现文件复制的方法
public void copyFileWithBuffered(String srcPath,String destPath){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//1.造文件
File srcFile = new File(srcPath);
File destFile = new File(destPath);
//2.造流
//2.1 造节点流
FileInputStream fis = new FileInputStream((srcFile));
FileOutputStream fos = new FileOutputStream(destFile);
//2.2 造缓冲流
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//3.复制的细节:读取、写入
byte[] buffer = new byte[1024];
int len;
while((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
} } catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源关闭
//要求:先关闭外层的流,再关闭内层的流
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
} }
if(bis != null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略. //fos.close(); //fis.close(); } } `
处理流之二:转换流的使用
1.转换流:属于字符流
- InputStreamReader:将一个字节的输入流转换为字符的输入流
- OutputStreamWriter:将一个字符的输出流转换为字节的输出流
2.作用:提供字节流与字符流之间的转换
`//参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
3.解码:字节、字节数组 --->字符数组、字符串
编码:字符数组、字符串 ---> 字节、字节数组
4.字符集
-
ASCII:美国标准信息交换码。用一个字节的7位可以表示。
-
ISO8859-1:拉丁码表。欧洲码表。用一个字节的8位表示。
-
GB2312:中国的中文编码表。最多两个字节编码所有字符
-
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
-
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
-
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
示例代码:
`/* 此时处理异常的话,仍然应该使用try-catch-finally
综合使用InputStreamReader和OutputStreamWriter
*/
@Test public void test2() throws Exception {
//1.造文件、造流
File file1 = new File("dbcp.txt");
File file2 = new File("dbcp_gbk.txt");
FileInputStream fis = new FileInputStream(file1); FileOutputStream fos = new FileOutputStream(file2);
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
//2.读写过程
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1){
osw.write(cbuf,0,len); }
//3.关闭资源
isr.close(); osw.close();
} `
day28
Path
-
jdk 7.0 时,引入了 Path、Paths、Files三个类。
-
此三个类声明在:java.nio.file包下。
-
Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
-
如何实例化Path:使用Paths.
static Path get(String first, String … more)
: 用于将多个字符串串连成路径
static Path get(URI uri)
: 返回指定uri对应的Path路径
如何使用Paths实例化Path
示例代码:
`@Test
public void test1() {
Path path1 = Paths.get("d:\\nio\\hello.txt");
//
new File(String filepath) Path path2 = Paths.get("d:\\", "nio\\hello.txt");
//
new File(String parent,String filename); System.out.println(path1); System.out.println(path2);
Path path3 = Paths.get("d:\\", "nio"); System.out.println(path3); } `
Path中的常用方法
String toString()
: 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path)
: 判断是否以 path 路径开始
boolean endsWith(String path)
: 判断是否以 path 路径结束
boolean isAbsolute()
: 判断是否是绝对路径
Path getParent()
:返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot()
:返回调用 Path 对象的根路径
Path getFileName()
: 返回与调用 Path 对象关联的文件名
int getNameCount()
: 返回Path 根目录后面元素的数量
Path getName(int idx)
: 返回指定索引位置 idx 的路径名称
Path toAbsolutePath()
: 作为绝对路径返回调用 Path 对象
Path resolve(Path p)
:合并两个路径,返回合并后的路径对应的Path对象
File toFile()
: 将Path转化为File类的对象
File file = path1.toFile();//Path--->File的转换 Path newPath = file.toPath();//File--->Path的转换
Files工具类的使用:操作文件或目录的工具类
以这两条代码为例,导入文件路径:
`Path path1 = Paths.get("d:\\nio", "hello.txt"); Path path2 = Paths.get("atguigu.txt"); `
`Path copy(Path src, Path dest, CopyOption … how)`: 文件的复制
说明:要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求。
`Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING); `
`Path createDirectory(Path path, FileAttribute<?> … attr)` : 创建一个目录
说明:要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
`Path path3 = Paths.get("d:\\nio\\nio1"); Files.createDirectory(path3); `
`Path createFile(Path path, FileAttribute<?> … arr)` : 创建一个文件
说明:要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
`Path path4 = Paths.get("d:\\nio\\hi.txt"); Files.createFile(path4); `
`void delete(Path path)` : 删除一个文件/目录,如果不存在,执行报错
`Files.delete(path4); `
`void deleteIfExists(Path path)`: Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
`Files.deleteIfExists(path3); `
`Path move(Path src, Path dest, CopyOption…how)`: 将 src 移动到 dest 位置
说明:要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
`Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE); `
`long size(Path path)`: 返回 path 指定文件的大小
`long size = Files.size(path2); System.out.println(size); `
`boolean exists(Path path, LinkOption … opts)`: 判断文件是否存在
`System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS)); `
`boolean isDirectory(Path path, LinkOption … opts)` : 判断是否是目录
说明:不要求此path对应的物理文件存在。
`System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS)); `
`boolean isRegularFile(Path path, LinkOption … opts)` : 判断是否是文件
`boolean isHidden(Path path)` : 判断是否是隐藏文件
说明:要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
`System.out.println(Files.isHidden(path1)); `
`boolean isReadable(Path path)` : 判断文件是否可读
`System.out.println(Files.isReadable(path1)); `
`boolean isWritable(Path path)` : 判断文件是否可写
`System.out.println(Files.isWritable(path1)); `
`boolean notExists(Path path, LinkOption … opts)`: 判断文件是否不存在
`System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS)); `
- StandardOpenOption.READ:表示对应的Channel是可读的。
- StandardOpenOption.WRITE:表示对应的Channel是可写的。
- StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
- StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
`InputStream newInputStream(Path path, OpenOption…how)`:获取 InputStream 对象
InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);
`OutputStream newOutputStream(Path path, OpenOption…how)`: 获取 OutputStream 对象
`OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE); `
`SeekableByteChannel newByteChannel(Path path, OpenOption…how)` : 获取与指定文件的连接,how 指定打开方式。
`SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); `
`DirectoryStream<Path> newDirectoryStream(Path path)` : 打开 path 指定的目录
`Path path2 = Paths.get("e:\\teach"); DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2); Iterator<Path> iterator = directoryStream.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } `
对象流的使用
1.ObjectInputStream 和 ObjectOutputStream
2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
Person.java
`import java.io.Serializable; /**
Person需要满足如下的要求,方可序列化
1.需要实现接口:Serializable
2.当前类提供一个全局常量:serialVersionUID
3.除了当前Person类需要实现Serializable接口之外,还必须保证其内部所有属性 也必须是可序列化的。(默认情况下,基本数据类型可序列化)
补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
@author shkstart
@create 2019 上午 10:38 */
public class Person implements Serializable{
public static final long serialVersionUID = 475463534532L;
private String name;
private int age;
private int id;
private Account acct;
public Person(String name, int age, int id) {
this.name = name;
this.age = age;
this.id = id; }
public Person(String name, int age, int id, Account acct) {
this.name = name;
this.age = age;
this.id = id;
this.acct = acct; }
@Override
public String toString() {
return "Person{" + "name='" + name + ''' + ", age=" + age + ", id=" + id + ", acct=" + acct + '}';
}
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 int getAge() { return age; }
public void setAge(int age) { this.age = age; } public Person(String name, int age) { this.name = name; this.age = age; }
public Person() { } }
class Account implements Serializable{
public static final long serialVersionUID = 4754534532L;
private double balance;
@Override
public String toString() {
return "Account{" + "balance=" + balance + '}';
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(double balance) { this.balance = balance;
}
} `
4.序列化机制:
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
RandomAccessFile的使用
- RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
- RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
- 如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
- 可以通过相关的操作,实现RandomAccessFile“插入”数据的效果
示例代码:
` /* 使用RandomAccessFile实现数据的插入效果 */
@Test
public void test3() throws IOException {
RandomAccessFile raf1 = new
RandomAccessFile("hello.txt","rw");
raf1.seek(3); //将指针调到角标为3的位置
//保存指针3后面的所有数据到StringBuilder中
StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
byte[] buffer = new byte[20];
int len;
while((len = raf1.read(buffer)) != -1){
builder.append(new String(buffer,0,len)) ; }
//调回指针,写入“xyz”
raf1.seek(3);
raf1.write("xyz".getBytes());
//将StringBuilder中的数据写入到文件中
raf1.write(builder.toString().getBytes());
raf1.close();
} `
网络编程
一、网络编程中有两个主要的问题:
- 1.如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
- 2.找到主机后如何可靠高效地进行数据传输
二、网络编程中的两个要素:
- 1.对应问题一:IP和端口号
- 2.对应问题二:提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
三、通信要素一:IP和端口号
1.IP:唯一的标识 Internet 上的计算机(通信实体)
2.在Java中使用InetAddress类代表IP
3.IP分类:IPv4 和 IPv6 ; 万维网 和 局域网
4.域名: www.baidu.com www.mi.com www.sina.com www.jd.com www.vip.com
5.本地回路地址:127.0.0.1 对应着:localhost
6.如何实例化InetAddress:两个方法:
getByName(String host)
getLocalHost()
两个常用方法:
getHostName() 域名
getHostAddress() 主机地址
7.端口号:正在计算机上运行的进程。
- 要求:不同的进程有不同的端口号
- 范围:被规定为一个 16 位的整数 0~65535。
8.端口号与IP地址的组合得出一个网络套接字:Socket
TCP的3个例子
通过三个例子学习客户端与服务端的交互
例子1: 客户端发送信息给服务端,服务端将数据显示在控制台上
`public class TCPTest1 {
//客户端
@Test
public void client() {
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("192.168.14.100");
socket = new Socket(inet,8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户mm".getBytes()); } catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//服务端
@Test
public void server() {
ServerSocket ss = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ss = new ServerSocket(8899);
//2.调用accept()表示接收来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//不建议这样写,可能会有乱码
// byte[] buffer = new byte[1024];
// int len;
// while((len = is.read(buffer)) != -1){// String str = new String(buffer,0,len);
// System.out.print(str);
// }
//4.读取输入流中的数据
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[5];
int len;
while((len = is.read(buffer)) != -1){ baos.write(buffer,0,len); }
System.out.println(baos.toString());
System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");
} catch (IOException e) {
e.printStackTrace();
} finally {
if(baos != null){
//5.关闭资源
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) { e.printStackTrace();
}
}
if(ss != null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} `
总结:
客户端步骤分为:
- 创建Socket对象,指明服务器端的ip和端口号(关键字: Socket、InetAddress、getByName)
- 获取一个输出流,用于输出数据(关键字: OutputStream、 socket.getOutputStream)
- 写出数据的操作(关键字: getBytes)
- 关闭资源(后用先关,先关输出流,再关socket)
服务端步骤分为:
- 创建服务器端的ServerSocket,指明自己的端口号(关键字: ServerSocket)
- 调用accept()表示接收来自于客户端的socket(关键字: accept)
- 获取输入流(关键字: getInputStream)
- 读取输入流中的数据(关键字: ByteArrayOutputStream)
- 关闭资源
例子2:客户端发送文件给服务端,服务端将文件保存在本地。
`public class TCPTest2 {
/* 这里涉及到的异常,应该使用try-catch-finally处理 */
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os =
socket.getOutputStream();
//3.
FileInputStream fis = new
FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){ os.write(buffer,0,len); }
//5.
fis.close();
os.close();
socket.close(); }
/* 这里涉及到的异常,应该使用try-catch-finally处理 */
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty1.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
}
//6.
fos.close();
is.close();
socket.close();
ss.close();
}
} `
分析:步骤与例子1是一样的,差别在于服务端保存文件的IO方法。
例子3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
`public class TCPTest3 {
/* 这里涉及到的异常,应该使用try-catch-finally处理 */
@Test
public void client() throws IOException {
//1.
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
//2.
OutputStream os = socket.getOutputStream(); //3.
FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
//4.
byte[] buffer = new byte[1024];
int len;
while((len = fis.read(buffer)) != -1){ os.write(buffer,0,len);
}
//关闭数据的输出
socket.shutdownOutput();
//5.接收来自于服务器端的数据,并显示到控制台上
InputStream is = socket.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] bufferr = new byte[20];
int len1;
while((len1 = is.read(buffer)) != -1){ baos.write(buffer,0,len1);
}
System.out.println(baos.toString());
//6.
fis.close();
os.close();
socket.close();
baos.close(); }
/* 这里涉及到的异常,应该使用try-catch-finally处理 */
@Test
public void server() throws IOException {
//1.
ServerSocket ss = new ServerSocket(9090);
//2.
Socket socket = ss.accept();
//3.
InputStream is = socket.getInputStream();
//4.
FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
//5.
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){ fos.write(buffer,0,len);
}
System.out.println("图片传输完成");
//6.服务器端给予客户端反馈
OutputStream os = socket.getOutputStream(); os.write("你好,美女,照片我已收到,非常漂亮!".getBytes()); /
/7.
fos.close();
is.close();
socket.close();
ss.close();
os.close();
}
} `
URL网络编程
1.URL:统一资源定位符,对应着互联网的某一资源地址
2.格式:
http://localhost:8080/examples/beauty.jpg?username=Tom
协议 主机名 端口号 资源地址 参数列表
- url.getProtocol( ): 获取该URL的协议名
- url.getHost( ): 获取该URL的主机名
- url.getPort( ): 获取该URL的端口号
- url.getPath( ): 获取该URL的文件路径
- url.getFile( ): 获取该URL的文件名
- url.getQuery( ): 获取该URL的查询名
`public class URLTest1 {
public static void main(String[] args) {
HttpURLConnection urlConnection = null;
InputStream is = null;
FileOutputStream fos = null;
try {
URL url = new URL("http://localhost:8080/examples/beauty.jpg"); urlConnection = (HttpURLConnection)
url.openConnection();
urlConnection.connect();
is = urlConnection.getInputStream();
fos = new FileOutputStream("day10\\beauty3.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){ fos.write(buffer,0,len);
}
System.out.println("下载完成");
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace(); }
}
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(urlConnection != null){
urlConnection.disconnect(); }
}
}
} `
UDP协议的网络编程
`public class UDPTest {
//发送端
@Test
public void sender() throws IOException {
DatagramSocket socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] data = str.getBytes();
InetAddress inet = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(data,0,data.length,inet,9090);
socket.send(packet);
socket.close(); }
//接收端
@Test
public void receiver() throws IOException { DatagramSocket socket = new
DatagramSocket(9090);
byte[] buffer = new byte[100]; DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(),0,packet.getLength())); socket.close();
} } `
day29
反射初步了解
在Person类外部,不可以通过Person类的对象调用其内部私有结构。可以通过反射,创建Person类的对象,调用对象指定的属性、方法,甚至可以通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性。
疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用那个?
建议:直接new的方式。
什么时候会使用:反射的方式。 反射的特征:动态性
疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾。
关于java.lang.Class类的理解
- 类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。 - 换句话说,Class的实例就对应着一个运行时类。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式 来获取此运行时类。
获取Class的实例的方式
(前三种方式需要掌握)
- 方式一:调用运行时类的属性:.class
- 方式二:通过运行时类的对象,调用getClass()
- 方式三:调用Class的静态方法:forName(String classPath) (常用)
- 方式四:使用类的加载器:ClassLoader (了解)
`public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
//clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用类的加载器:ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4); System.out.println(clazz1 == clazz4);
} `
Class实例可以是哪些结构
万事万物皆对象
`@Test
public void test4(){
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11); //true
} `
newInstance
newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
1.运行时类必须提供空参的构造器
2.空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
person obj = clazz.newInsstance;
System.out.println(obj);
public class test{
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();//已经过时
Person person1 = clazz.getDeclaredConstructor().newInstance();
System.out.println(person);
System.out.println(person1);
}
}
class Person{
String name;
int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(){
System.out.println("Person空参构造器");
}
@Override
public String toString() {
return this.name + this.age;
}
}
了解类的加载器
- 对于自定义类,使用系统类加载器进行加载
- 调用系统类加载器的getParent():获取扩展类加载器
- 调用扩展类加载器的getParent():无法获取引导类加载器
- 引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类的加载器。
`@Test
public void test1(){
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader =
ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 =
classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器 //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 =
classLoader1.getParent();
System.out.println(classLoader2); //NULL;
ClassLoader classLoader3 =
String.class.getClassLoader();
System.out.println(classLoader3); //NULL;
} `
Properties
Properties:用来读取配置文件
`@Test
public void test2() throws Exception {
Properties pros = new Properties();
//此时的文件默认在当前的module下。
//读取配置文件的方式一:
//FileInputStream fis = new FileInputStream("jdbc.properties"); //FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); //pros.load(fis);
//读取配置文件的方式二:使用ClassLoader
//配置文件默认识别为:当前module的src下
ClassLoader classLoader =
ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
System.out.println("user = " + user + ",password = " + password); } `
总结:
- 注意两种方式读取文件的路径不同,方式一在当前module下,方式二在当前module的src下
- 使用getResourceAsStream读取文件
- 将要读去的数据写入一个.properties文件中,创建方式New --> Resource Bundle
获取运行时类
获取属性结构
getFields():获取当前运行时类及其父类中声明为public访问权限的属性
getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
获取权限修饰符 数据类型 变量名
getModifiers():权限修饰符
getType():数据类型
getName():变量名
public class test{
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields){
int modify = f.getModifiers();
System.out.println(modify); System.out.println(Modifier.toString(modify));
System.out.println(f.getType());
System.out.println(f.getName());
System.out.println("----------------------");
}
}
}
class Person{
String name;
protected int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(){
System.out.println("Person空参构造器");
}
@Override
public String toString() {
return this.name + this.age;
}
}
获取方法结构
getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
public class test{
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m : declaredMethods){
System.out.println(m);
int modifiers = m.getModifiers();
System.out.println(modifiers);
System.out.println(Modifier.toString(m.getModifiers()));
System.out.println(m.getName());
System.out.println(m.getReturnType());
}
}
}
class Person{
String name;
protected int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(){
System.out.println("Person空参构造器");
}
@Override
public String toString() {
return this.name + this.age;
}
}
获取注解、权限修饰符、方法名
例如:
``/* @Xxxx 权限修饰符 返回值类型 方法名(参数类型1 形参名1,...) throws XxxException{} */
getAnnotations(): 获取方法声明的注解
Modifier.toString(m.getModifiers()): 权限修饰符
public class test{
public static void main(String[] args) throws Exception {
Class<Person> clazz = Person.class;
//获取指定的属性:要求运行时类中属性声明为public
//换成getDeclaredField(string fieldName)并加入name.setAccessible(true);
Field name = clazz.getDeclaredField("name");
System.out.println(name);
//反射创建对象
Person person = clazz.getDeclaredConstructor().newInstance();
//设置值
name.set(person,"Tom");
//输出值
String str = (String)name.get(person);
System.out.println(str);
}
}
class Person{
String name;
protected int age;
Person(String name,int age){
this.name = name;
this.age = age;
}
public Person(){
System.out.println("Person空参构造器");
}
@Override
public String toString() {
return this.name + this.age;
}
}
返回值类型、形参列表
获取的权限修饰符比较特殊, getModifiers()返回的是int类型(如1, 2, 3, 4),这些数字分别代表public, protected等
使用Modifier.toString将它转化为字符串形式的权限修饰符 getReturnType().getName():返回值类型
getName()
形参列表
` Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0; i < parameterTypes.length;i++){
//最后一个显示情况;
if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
} `
抛出的异常
` Class[] exceptionTypes = m.getExceptionTypes(); if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
//最后一个显示情况;
if(i == exceptionTypes.length - 1){ System.out.print(exceptionTypes[i].getName()); break; }
System.out.print(exceptionTypes[i].getName() + ",");
}
} `
获取构造器
获取运行时类的父类和父类的泛型
获取运行类的接口、所在包、注解
调用运行时类
调用运行时类属性
不推荐使用
一般用下边的方式:
调用运行时类的指定方法
调用静态方法
获取指定构造器
使用java反射进行动态代理
public class test{
public static void main(String[] args) throws Exception {
Superman superman = new Superman();
Human proxyInstance = (Human)ProxyTest.getProxyInstance(superman);
String food = proxyInstance.eat("树叶");
System.out.println(food);
}
}
//接口
interface Human{
String eat(String food);
}
//被代理类
class Superman implements Human{
@Override
public String eat(String food) {
return "超人吃"+food;
}
}
class ProxyTest{
public static Object getProxyInstance(Object obj){
//obj:被代理类的对象
MyInvocationHandler handler = new MyInvocationHandler();
handler.bind(obj);
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}
}
class MyInvocationHandler implements InvocationHandler{
private Object obj;//需要使用被代理类的对象进行赋值
public void bind(Object obj){
this.obj = obj;
}
//当我们通过代理类的对象,调用a方法时,就会自动调用如下的方法:
// invoke()
//将被代理类的方法a的功能声明在invoke()方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method:即为代理类对象调用的方法,此方法也就作为被代理对象要调用的方法 obj:被代理类的对象
Object invoke = method.invoke(obj, args);
return invoke;
}
}
day30
java8新特性
速度更快
代码更少(增加了新的语法: Lambda表达式)
强大的Stream API
便于并行
最大化减少空指针异常:Optional
Nashorn引擎,允许在JVM上运行JS应用
lambda
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
对接口的要求:
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
Lambda表达式的使用
1.举例:(o1,o2)-> Integer.compare(o1,o2);
2.格式:
Lambda操作符或箭头操作符
左边: Lambda形参列表(其实就是接口中的抽象方法的形参列表)
右边: Lambda体(其实就是重写的抽象方法的方法体)
总结:
左边: lambda形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,其一对()也可以省略
右边:Lambda体应该使用一对{}包裹;如果Lambda体只有一条执行语句(可能是return语句),可以省略这一对{}和return关键字
以下可以使用lambda表达式
方法引用
使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以方法引用,也是函数式接口的实例
使用格式:类(或对象)::方法名
方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的形参列表和返回值类型相同
Stream API
实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Redis等,而这些NoSQL的数据就需要Java层面去处理。
Stream和 Collection集合的区别:Collection是一种静态的内存数据结构,而 Stream是有关计算的。前者是主要面向内存,存储在内存中后者主要是面向CPU,通过CPU实现计算。
Optional
Optiona< T >类java.util.Optional)是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
Optional.of(T t) :创建一个 OptionaL实例,t必须非空;
Optional. empty() : 创建一个空的Optional实例
Optional.ofNullable(T t): t可以为nuLL;
java9新特性
在这里插入图片描述
模块化系统
不同模块下的不同包可以进行相互调用。需要在包下创建modile-info.java
需要暴露包则在自己的modile-info.java写入
exports 包名
需要调用包则在自己的modile-info.java写入
requires 模块名
REPL工具
jShell:像Python和Scala之类的语言早就有交互式编程环境REPL(read - evaluate - print -loop)了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而之前的Java版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现。
接口中允许声明私有方法
钻石操作符使用升级
我们将能够与匿名实现类共同使用钻石操作符(diamond operator)在Java 8中如下的操作是会报错的
Comparator<Object>com = new Comparator<>(){
@Override
public int compare(Object o1,Object o2){
return o;
}
};
try语法升级
Java8中,可以实现资源的自动关闭,但是要求执行后必须关闭的所有资源必;
java9中资源关闭操作:需要自动关闭的资源的实例化可以放在try的一对小括号外面;此时的资源属性是常量,声明为final,不能修改。
须在try子句中初始化,否则编译不通过。java8如下例所示:
try(InputStreamReader reader = new InputStreamReader(System.in)){
//读取数据细节省略
}catch(IOException e){
e.printStackTrace();
}
创建只读集合
.of操作
public class test{
public static void main(String[] args) throws Exception {
List<Integer> list = List.of(1,2,3);
list.add(3);
}
}
InputStream新方法:tranferTo()
public class test{
public static void main(String[] args) throws Exception {
try(FileInputStream fileInputStream = new FileInputStream("test.txt");
FileOutputStream fileOutputStream = new FileOutputStream("test1.txt")){
//把输入流中的所有数据直接自动的复制到输出流中
fileInputStream.transferTo(fileOutputStream);
}catch (IOException e){
e.printStackTrace();
}
}
}
增强型stream API
Java10新特性
局部变量的类型推断
public class test{
public static void main(String[] args) throws Exception {
var number = 10;
var list = new ArrayList<Integer>;
list.add(2);
for (var n : list){
System.out.println(n);
}
}
}
集合中新增copyof()
copyOf于(Xxx coll):如果参数coll本身就是一个只读集合,则copyof()返回值即为当前的coll;如果参数coll不是一个只读集合,则copyof()返回一个新的集合,这个集合是只读的。
Java11新特性
新增字符串处理方法
optional增强
or和orelse的区别:
or返还的是一个Optional,而orelse返回的是values
局部变量类型推断升级
使用var的好处是在使用lambda表达式的时候给参数加上注解;
Consumer<String> consumer = (@Deprecated var t) -> System.out.println(t.toUpperCase());
Httpclient
public class test{
public static void main(String[] args) throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://www.baidu.com")).build();
HttpResponse.BodyHandler<String> responseBodyHandler = HttpResponse.BodyHandlers.ofString();
HttpResponse<String> response = httpClient.send(request, responseBodyHandler);
String body = response.body();
System.out.println(body);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)