Java下篇
常用类
字符串
String
String类不可被继承
String实现了Serializable接口:表示字符串支持序列化。实现了Comparable接口:表示可以比较大小
String内部定义了
final char[] value
用于存储字符串String:代表不可变的字符序列(对String重新赋值、进行连接操作、replace时,会重新指定内存区域)
创建
通过字面量(
String s = "abc"
)的方式给字符串赋值,此时的字符串值声明在字符串常量池中(位于方法区,字符串常量池中是不会存储相同内容的字符串的,所以这种方法定义的相同字符串的地址相同)通过new的方式创建,数据存储在堆空间(相同内容的字符串地址不同)
常量与常量的拼接结果在常量池;只要其中有一个是变量,结果就在堆中;如果拼接的结果调用intern()
方法,返回值就在常量池中。
String s1 = "javaEE";
String s2 = "hadoop";
String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false
String s8 = s5.intern();
System.out.println(s3 == s8);//true
常用方法
int length():返回字符串的长度: return value.length
char charAtint index):返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串: return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(object obj):比较字符串的内容是否相同
boolean equalslgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginlndex):返回一个新的字符串,它是此字符串的从beginindex开始截取到最后的一个子字符串。
String substring(int beginindex, int endlndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endindex(不包含)的一个子字符串。
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromindex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastlndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastlndexOf(String str,int fromindex):返回指定子字符串在此字符串中最后次出现处的索引,从指定的索引开始反向搜索
// 注: indexOf和lastlndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex,String replacement):使用给定的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex,String replacement):使用给定的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式
String split(String regex):根据给定正则表达式的匹配拆分此字符串。
String split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中
String和char[]之间的转换:
String str1 = "abc123";
char[] charArray = str.toCharArray();
char[] arr = new char[]('a', 'b' ,'c');
String str2 = new String(arr);
String与byte[]之间的转换:
String str1 = "abc123中国";
byte[] bytes1 = str1.getBytes(); // 使用默认的字符集进行编码
byte[] bytes2 = str1.getBytes("gbk"); // 使用gbk字符集进行编码
String str2 = new String(bytes1); // 使用默认字符集进行解码
String str3 = new String(bytes2, "gbk");
StringBuffer和StringBuilder
异同
- String:不可变;底层使用char[]数组
- StringBuffer:可变;线程安全;效率低;底层使用char[]数组
- StringBuilder:可变;线程不安全;效率高;底层使用char[]数组
源码分析
StringBuffer sb1 = new StringBuffer(); // 底层创建了长度为16的char[]
StringBuffer sb1 = new StringBuffer("abc"); // 底层创建了长度为"abc".length()+16的char[]
// 建议使用以下构造器
StringBuffer(int capacity)
StringBuilder(int capacity)
如果要添加的数据底层数组装不下了,就要扩容。默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新数组。
常用方法
虽然有返回值(方法链原理),但是字符串本身其实也变了。
int indexOf(String str)
String substring(int start,int end) // 不改变原来的字符串
int length()
char charAt(int n )
void setCharAt(int n ,char ch)
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse():把当前字符序列逆转
日期和时间(JDK8以前)
System
从1970年1月1日到现在的毫秒数。
long time = System.currentTimeMillis();
Date
有两个Date类,还是继承关系。
java.util.Date
java.sql.Date // 数据库中的日期变量
构造器和方法
import java.util.Date;
Date date1 = new Date(); // 空参构造器代表当前时间
sout(date1.toString()); // 输出当前的日期和时间
sout(date1.getTime()); // 输出毫秒数,同currentTimeMillis()
Date date2 = new Date(1550306204104L); // 参数为毫秒数
java.sql.Date date3 = new java.sql.Date(1550306204104L);
sout(date3); // 形如1971-2-13
// 转换
Date date6 = new Date();
java.sql.Date date7 = new java.sql.Date(date6.getTime());
SimpleDateFormat
// 默认构造器实例化
SimpleDateFormat sdf = new SimpleDateFormat();
// 格式化
Date date1 = new Date();
String format = sdf.format(date1);
sout(format);
// 解析
String str = "19-08-09 上午11:43";
Date date2 = sdf.parse(str);
sout(date2);
/********************************************/
// 带参构造器
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String format1 = sdf1.format(date1);
sout(format1); //2019-02-18 11:48:27
// 解析的字符串也要满足指定的格式
Calendar
抽象类。
注意月份从0开始,星期从1开始。
// 实例化
// 方法一:创建子类(GregorianCalendar)的对象
// 方法二:调用静态方法
Calendar calendar = Calendar.getInstance(); // 实际上calendar还是GregorianCalendar对象
// 方法
// get
int days = calendar.get(Calendar.DAY_OF_MONTH);
// set 会改变calendar本身
calendar.set(Calendar.DAY_OF_MONTH, 22);
// add 可以加负数
calendar.add(Calendar.DAY_OF_MONTH, 3);
// 转换
Date date = calendar.getTime();
Date date1 = new Date();
calendar.setTime(date1);
日期和时间(JDK8)
LocalDate、LocalTime、LocalDateTime
三个类都有下列方法。
方法 | 描述 |
---|---|
now()、now(Zoneld zone) | 静态方法,根据当前时间创建对象/指定时区的对象 |
of() | 静态方法,根据指定日期/时间创建对象 |
getDayOfMonth()、getDayOfYear() | 获得月份天数(1-31)/获得年份天数(1-366) |
getDayOfWeek() | 获得星期几(返回一个 DayOfWeek 枚举值) |
getMonth() | 获得月份返回一个 Month 枚举值 |
getMonthValue()、getYear() | 获得月份(1-12)/获得年份 |
getHour()、getMinute()、getSecond() | 获得当前对象对应的小时、分钟、秒 |
withDayOfMonth()、withDayOfYear()、withMonth()、withYear() | 将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象 |
plusDays()、plusWeeks()、plusMonths()、plusYears()、plusHours() | 向当前对象添加几天、几周、几个月、几年、几小时 |
minusMonths()、minusWeeks()、minusDays()、minusYears()、minusHours() | 从当前对象减去几月、几周、几天、几年、几小时 |
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 1, 13, 43, 25); // 没有偏移量(年份月份和日常一致)
with、plus、minus方法,有返回值,本身不变
Instant
方法 | 描述 |
---|---|
now() | 静态方法,返回默认UTC时区的Instant类的对象 |
ofEpochMilli(long epochMilli) | 静态方法,返回在1970-01-01 00:00:00基础上加上指定毫秒数之后的Instant类的对象 |
atOffset(ZoneOffset offset) | 结合即时的偏移来创建一个 OffsetDateTime |
toEpochMilli() | 返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳 |
Instant instant = Instant.now();
sout(instant); // 返回的是格林威治时间
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
sout(offsetDateTime); // 东8区的时间
DateTimeFormatter
java.time.format.DateTimeFormatter
类,该类提供了三种格式化方法:
- 预定义的标准格式。如:
ISO_LOCAL_DATE_TIME
、ISO_LOCAL_DATE
、ISO_LOCAL_TIME
- 本地化相关的格式。如:
ofLocalizedDateTime(FormatStyle.LONG)
- 自定义的格式。如:
ofPattern("yyyy-MM-dd hh:mm:ss E")
方法 | 描述 |
---|---|
ofPattern(String pattern) | 静态方法,返回一个指定字符串格式的DateTimeFormatter |
format(TemporalAccessor t) | 格式化一个日期、时间、返回字符串 |
parse(CharSequence text) | 将指定格式的字符序列解析为一个日期、时间 |
// 方式一
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
sout(str1);
// 解析时格式也要对应,TemporalAccessor是一个接口
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
sout(parse);
// 方式二
// 本地化相关的格式。如: ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT:适用FLocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
// 格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2); //2019年2月18日 下午03时47分16秒
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle,MEDIUM);
// 格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
// 重点,方式三:自定义的格式。如: ofPattern("yyyy-MM-dd hh:mm:ss")
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
Java比较器
Comparable接口(自然排序)
如String、包装类等实现了Comparable接口,重写了compareTo()方法。
String[] arr = new String[]{"AA", "KK", "BB"};
Arrays.sort(arr);
sout(Arrays.toString(arr));
自定义类要比较就要实现接口。
class Goods implements Comparable {
String name;
int price;
@Override
public int compareTo(Object o) {
if (o instanceof Goods) {
Goods goods = (Goods) o;
// 方式一
if (this.price > goods.price) return 1;
else if (this.price < goods.price) return -1;
else return 0;
// 方式二
return Double.compare(this.price, goods.price);
}
throw new RuntimeException("类型错误");
}
}
// 使用泛型
class Goods implements Comparable<Integer> {
@Override
public int compareTo(Integer o) {
......
}
}
Comparator接口(定制排序)
String[] arr = new String[]{"AA", "KK", "BB"};
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return -(String) o1.compareTo((String) o2);
}
throw new RuntimeException("类型错误");
});
// 使用泛型
Arrays.sort(arr, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return -o1.compareTo(o2);
}
});
System
有三个属性in
、out
、err
。
String getProperty(String key)
:该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示。
属性名 | 属性说明 |
---|---|
java.version | Java运行时环境版本 |
java.home | Java安装目录 |
os.name | 操作系统的名称 |
os.version | 操作系统的版本 |
user.name | 用户的账户名称 |
user.home | 用户的主目录 |
user.dir | 用户的当前工作目录 |
枚举类
类的对象只有有限个
当需要定义一组常量时,强烈建议使用枚举类
如果枚举类只有一个对象,可以作为单例模式的实现方式
使用enum定义
定义的枚举类默认继承于java.lang.Enum
类。
enum Season {
// 1. 提供当前枚举类的对象,用逗号隔开,以分号结束
SPRING("春天", "春暖花开"),
SUMMER("夏天", "夏日炎炎"),
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "冰天雪地");
// 2. 声明Season对象的属性,用private final修饰
private final String seasonName;
private final String seasonDesc;
// 3. 私有化类的构造器并赋值
private Season(String seasonName, String seasonDecs) {
this.seasonName = seasonName;
this.seasonDecs = seasonDecs;
}
// 4. 定义其他方法(可不写)
public String getSeasonName() {
return seasonName;
}
}
更简洁的写法。
public enum Status {
FREE, BUSY, VOCATION;
}
// 可以用switch
switch (一个枚举类对象) {
case FREE: // 直接写名字
......
case BUSY:
......
}
常用方法
sout(Season.SPRING);
sout(Season.SPRING.toString()); // 返回枚举类对象常量的名称 SPRING
Season[] values = Season.values(); // 返回所有的枚举类对象
Season winter = Season.valueOf("WINTER"); // 提供字符串,返回同名对象(如果没有找到,会出异常)
实现接口
可以和普通类一样实现接口。
以下是枚举类特有的,可以为每一个对象实现不同的接口。
interface Info {
void show();
}
enum Season implement Info{
SPRING("春天", "春暖花开") {
@Override
void show() {
}
},
SUMMER("夏天", "夏日炎炎") {
@Override
void show() {
}
},
AUTUMN("秋天", "秋高气爽") {
@Override
void show() {
}
},
WINTER("冬天", "冰天雪地") {
@Override
void show() {
}
};
......
}
注解
内置注解
@Override
:方法重写(加了注解编译时就会进行校验是否是重写方法)@Deprecated
:修饰的结构已过时@SuppressWarnings
:抑制编译器警告
@SuppressWarnings("unused");
int num = 10; // 加了注解在idea中未使用的变量就不会变灰
@SuppressWarnings({"unused", "rawtypes"}); // 标注未使用泛型
ArrayList list = new ArrayList();
自定义注解
- 声明为
@interface
- 定义内部成员,通常用value表示
- 可以指定成员默认值,用
default
- 如果注解没有成员,起标识作用
public @interface MyAnnotation {
// 定义内部成员
String value();
String value() default "hello"; // 可以使用默认值
}
@MyAnnotation(value = "hello") // 如果y默认值则可以不指定值
class Person {
}
元注解
对现有的注解进行解释说明的注解。
Retention
@Retention
:只能用于修饰一个Annotation定义,用于指定该Annotation的生命周期,@Rentention包含一个RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:
RetentionPolicy.SOURCE
:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释RetentionPolicy.CLASS
:在class文件中有效(即class保留),当运行Java 程序时,JVM不会保留注解。默认值RetentionPolicy.RUNTIME
:在运行时有效(即运行时保留),当运行Java 程序时,JVM会保留注释。程序可以通过反射获取该注释
如JDK中SuppressWarnings
的定义:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
Target
@Target
:用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素。@Target 也包含一个名为 value
的成员变量。
取值(ElementType) | 取值(ElementType) | ||
---|---|---|---|
CONSTRUCTOR | 用于描述构造器 | PACKAGE | 用于描述包 |
FIELD | 用于描述域 | PARAMETER | 用于描述参数 |
LOCAL_VARIABLE | 用于描述局部变量 | TYPE | 用于描述类、接口(包括注解类型)或enum声明 |
METHOD | 用于描述方法 |
Documented和Inherited
@Documented
:用于指定被该元Annotation 修饰的Annotation
类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。
定义为Documented的注解必须设置Retention值为RUNTIME
@Inherited
:被它修饰的Annotation
将具有继承性。如果某个类使用了被@Inherited
修饰的Annotation,则其子类将自动具有该注解。
比如:如果把标有@lnherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解。实际应用中,使用较少
JDK8新特性
可重复注解
- 在MyAnnotation离明
@Repeatable
,成员值为MyAnnotations.class - MyAnnotation的Target和Retention等元注解与MyAnnotations相同
如上配置后则可以使用重复注解:
@MyAnnotation(value = "hello")
@MyAnnotation(value = "abc")
class Person {
}
类型注解
- ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)
- ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中
@Target({..., TYPE_PARAMETER, TYPE_USE})
如上配置则可以在以下位置注解:
class Generic <@MyAnnotation T> {
public void show() throws @MyAnnotation RuntimeException {
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}
}
集合
Collection接口
List接口
ArrayList
LinkedList
Vector
Set接口
HashSet
LinkedHashSet
TreeSet
Map接口
HashMap
LinkedHashMap
TreeMap
Hashtable
Properties
Collection接口
向Collection接口实现类的对象中添加数据时,要求添加的对象要重写equals()方法。
/* 接口中定义的方法 */
// add(Object o)
Collection coll = new ArrayList();
coll.add("AA");
coll.add(123);
coll.add(new Date());
// size()
sout(coll.size());
// addAll() 将一个集合的元素添加到另一个集合
Collection coll1 = new ArrayList();
coll1.add(456);
coll.addAll(coll1);
// isEmpty()
sout(coll.isEmpty);
// clear()
coll.clear();
// contains() 内部调用了equals方法
sout(coll.contains(123)); // true
// containsAll(Collection coll1) coll1的所有元素是否在coll中
coll.containsAll(coll1);
// remove(Object o)
coll.remove(123);
// removeAll(Collection coll1) 从当前集合移除coll1中所有的元素
coll.removeAll(Arrays.asList(123, 456));
// retainAll(Collection coll1) 更改当前集合的值为当前集合与coll1集合的交集
coll.retainAll(coll1);
// equals(Object o) 元素相同返回true
coll.equals(coll1);
// hashCode()
sout(coll.hashCode());
// toArray() 集合转数组
Object[] arr = coll.toArray();
// 数组转集合 Arrays.asList
List<String> list = Arrays.asList(new String[]{"aa", "bb"});
List list = Arrays.asList(new int[]{123, 456}); // 会把数组当作一个整体加进去
List list = Arrays.asList(new Integer[]{123, 456}); // 123和456分别加进去
// iterator() 返回Iterator接口的实例,用于遍历集合
Iterator接口
用于迭代Collection。集合对象每次调用iterator()方法都会得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
sout(iterator.next());
}
// 可以在遍历时删除元素
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if ("Tom".equals(obj)) {
iterator.remove(); // 此处调用的是Iterator中的remove方法
}
}
List接口
三个实现类的不同
ArrayList
:主要实现类;线程不安全,效率高;底层使用Object[]存储
LinkedList
:对于频繁的插入、删除操作,效率高;底层使用双向链表存储
Vector
:古老实现类;线程安全,效率低;底层使用Object[]存储
ArrayList
// JDK7
ArrayList list = new ArrayList(); // 底层创建大小为10的Object数组
list.add(123); // 如果添加导致底层数组容量不够,则扩容。默认扩容为原来的1.5倍并复制数据
// 建议使用带参构造器
ArrayList list = new ArrayList(int capacity);
// JDK8
ArrayList list = new ArrayList(); // 底层并没有创建数组
list.add(123); // 第一次调用add时,底层才创建长度为10的数组。其余操作同JDK7
LinkedList
Linkedlist list = new LinkedList(); // 内部声明了Node类型的first和last属性
list.add(123); // 将123封装到Node中,创建了Node对象
常用方法
ArrayList list = new ArrayList();
list.add(123);
// void add(int index, Object ele) 在index位置插入元素
list.add(1, "BB");
// boolean addAll(int index, Collection eles) 从index位置开始将eles中的元素添加
list.addAll(1, list1);
// object get(int index)
// int indexOf(Object obj)
// int lastIndexOf(Object obj)
// Object remove(int index) 还会返回删掉的元素
// Object set(int index, Object obj)
// List subList(int fromIndex, int toIndex)
Set接口
要求:向Set中添加的数据,其所在类一定要重写hashCode()和equals()方法,且这两个方法尽可能保持一致性(相同的对象有相同的哈希值)。
三个实现类的不同
HashSet
:主要实现类;线程不安全;可以存储null
LinkedHashSet
:HashSet
的子类;遍历内部数据时可以按照添加的顺序遍历
TreeSet
:可以按照添加对象的指定属性进行排序
HashSet
无序性:存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据哈希值。
不可重复性:保证添加的元素按照equals()判断时不能返回true
添加元素的过程
添加元素a,首先调用a所在类的hashCode()方法,计算a的哈希值,通过此哈希值计算出底层数组中的存放位置,判断此位置上是否已有元素:如果没有,添加成功;如果有元素b,则比较元素a与元素b的哈希值,如果不同则添加成功,哈希值相同并且通过equals方法比较仍相同则添加失败,通过equals方法比较不同还是添加成功。
如果底层数组同一位置有多个元素,采用链表存储(JDK7:a指向原来的元素;JDK8:原来的元素指向a)。
重写hashCode方法
@Override
public boolean equals(Object o) {
System.out.printin("User equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
LinkedHashSet
在添加数据的同时,每个数据还维护了两个引用,记录此数据前后的数据。对于频繁的遍历操作,效率高。
TreeSet
向TreeSet中添加的数据,要求是相同类的对象。
如果是自定义类,要实现Comparable接口或Comparator接口。
- 自然排序(Comparable接口)中,比较两个对象是否相同的标准为compareTo是否为0。
- 定制排序(Comparator接口)中,比较两个对象是否相同的标准为compare是否为0。
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
......
}
}
TreeSet set = new TreeSet(); // 构造器无参数就按自然排序
TreeSet set = new TreeSet(com); // 构造器有参数就按定制排序
去重
HashSet set = new HashSet();
set.addAll(要去重的List);
Map接口
Map
HashMap:主要实现类;线程不安全,效率高;可以存储null的key和value
LinkedHashMap:可以按照添加的顺序遍历(类似LinkedHashSed)
TreeMap:保证按照添加的key-value进行排序,实现排序遍历
Hashtable:古老的实现类;线程不安全,效率低;不可以存储null的key和value
Properties:常用来处理配置文件,key和value都是String类型
HashMap
一个键值对构成一个Entry对象,Entry是无序的不可重复的,使用Set存储所有的entry。
要求key所在的类要重写equals()方法和hashCode()方法,value所在的类要重写equals()方法。
底层原理
JDK7
// 实例化,底层创建长度为16的Entry[] table
HashMap map = new HashMap();
/*首先调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
如果此位置为空,添加成功 ----情况1
如果不为空,比较key1和已经存在的数据的哈希值
如果key1和已存在的哈希值都不同,添加成功 ----情况2
如果key1的哈希值和已存在的某一个数据的哈希值相同,继续通过equals方法比较
如果为false,添加成功 ----情况3
如果为true,使用value1替换原来的value
对于情况2和3,此时key1-value1和原来的数据以链表方式存储
在添加过程中,会涉及到扩容问题,默认扩容(超出临界值且当前要存放的位置非空)为原来容量的2倍并复制数据
*/
map.put(key1, value1);
JDK8
// JDK8底层的数组是Node[]而不是Entry[]
// new HashMap()时底层没有创建数组
// 首次put()时,才创建长度为16的数组
// JDK7底层的结构只有:数组+链表。JDK8:数组+链表+红黑树
// 当数组的某一个索引位置上的元素以链表的形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所有数据改为使用红黑树存储(如果某一位置数据个数 > 8 但数组长度 <= 64,则扩容就可以了)
重要常量
常量 | 描述 |
---|---|
DEFAULT_INITIAL_CAPACITY | HashMap的默认容量,16 |
MAXIMUM_CAPACITY | HashMap的最大支持容量,2^30 |
DEFAULT_LOAD_FACTOR | HashMap的默认加载因子,默认0.75。 |
TREEIFY_THRESHOLD | Bucket中链表长度大于该默认值,转化为红黑树 |
UNTREEIFY_THRESHOLD | Bucket中红黑树存储的Node小于该默认值,转化为链表 |
MIN_TREEIFY_CAPACITY | 桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。) |
table | 存储元素的数组,总是2的n次幂 |
entrySet | 存储具体元素的集 |
size | HashMap中存储的键值对的数量 |
modCount | HashMap扩容和结构改变的次数 |
threshold | 扩容的临界值,=容量*填充因子 |
loadFactor | 填充因子 |
LinkedHashMap
Entry节点多了before和after两项。
常用方法
// 添加、删除、修改操作
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集合
// entrySet()使用示例
Set entrySet = map.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
Map.Entry entry = (Map.Entry) obj;
sout(entry.getKey() + entry.getValue());
}
TreeMap
要求key必须是由同一个类创建的对象(按照key排序)。
自然排序或定制排序,同TreeSet。
Properties
创建一个配置文件xxx.properties。
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("xxx.properties");
pros.load(fis);
String name = pros.getProperty("name");
Collections工具类
可以操作Set、List、Map。
// 排序操作(均为static方法):
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方法要求dest的size大于等于src,通常的做法为:
List dest = Arrays.asList(new Object[src.size()]);
Collections.copy(dest, src);
List list1 = Collections.synchronizedList(list); // 返回的list1就是线程安全的
泛型(Generic)
实例化集合类时,可以指定具体的泛型类型。指定后,内部结构(方法、属性、构造器)使用到泛型的位置都会实例化为指定的类型。
泛型的类型必须是类。
如果实例化时没有指明泛型的类型,默认为Object类型。
自定义泛型
可以自定义泛型类、接口、方法。
class Order<T> {
String name;
T orderT;
public Order(String name, T orderT) {
}
public T getOrderT() {
}
}
注意
- 静态方法不能使用泛型
- 声明构造器的时候不加<>,但是new的时候要加
public Order() {}; // 声明
Order<String> order = new Order<>();
- 异常类不能是泛型的
- 声明数组
// 错误
T[] arr = new T[10];
// 正确
T[] arr = (T[]) new Object[10];
- 子类继承父类的泛型
class Father<T1, T2> {}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {} // 等价于class Son extends Father<Object,Object>
// 2)具体类型
class Son2 extends Father<Integer, String> {}
//子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {}
class Father<T1,T2> {}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son<A, B> extends Father {} // 等价于class Son extends Father<Object,Object>
// 2)具体类型
class Son2<A, B> extends Father<Integer, String> {}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {}
// 2)部分保留
class Son4<T2,A, B> extends Father<Integer, T2> {}
泛型方法
并不是方法中使用了泛型就叫泛型方法。泛型方法与类的泛型没有关系。
泛型方法可以是静态的。
public <E> List<E> copyFromArrayToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e: arr) {
list.add(e);
}
return list;
}
// 调用(以下Integer可以换为任意类)
Integer[] arr = new Integer[]{1, 2, 3};
List<Integer> list = order.copyFromArrayToList(arr);
泛型在继承方面的体现
类A是类B的父类,G<A>
和G<B>
二者不具备子父类关系(二者共同的父类是G<?>
),A<G>
是B<G>
的父类。
通配符
{
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null; // list相对于list1和list2的父类
list = list1;
list = list2;
print(list);
// 不能向list中添加数据(除null)
// 可以读取数据,读取的类型为Object
Object o = list.get(0);
}
public void print(List<?> list) {
Iterator<?> it = list.iterator();
while (it.hasNext()) {
Objecct obj = it.next();
sout(obj);
}
}
有限制条件的通配符
<? extends Person>
:可以赋Person和Person的子类<? super Person>
:可以赋Person和Person的父类
// Person是Student的父类
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Strudent> list3 = null;
List<Person> list4 = null;
List<Object> list5 = null;
list1 = list3; // 对
list1 = list4; // 对
list1 = list5; // 错
list2 = list3; // 错
list2 = list4; // 对
list2 = list5; // 对
// 可以读取
Person p = list1.get(0); // 最大就是Person
Object o = list2.get(0); // 最大是Object
// list2可以写入Person和Person的子类
list2.add(new Person());
list2.add(new Student());
IO流
File类
File类的一个对象代表一个文件或一个文件夹。
不涉及文件内容的读取和写入可以用File中的方法。
构造器
public File(String pathname)
:以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。public File(String parent, String child)
:以parent为父路径,child为子路径创建File对象。public File(File parent, String child)
:根据一个父File对象和子文件路径创建File对象
File file1 = new File("hello.txt");
// 在单元测试方法中这样写的路径是相对于当前module的路径(hello.txt与module下的src文件夹同级)
// 在main方法中这样写的路径是相对于当前工程的路径
File file2 = new File("d:\\study", "java"); // 代表parent(第一个参数所指)路径下的child(一个文件或文件夹)
File file3 = new File(file2, "hi.txt"); // 在file2目录下的hi.txt文件
根据操作系统(windos下\\
,unix下/
)动态提供分隔符:
File file1 = new File("d:" + File.separator + "study" + File.separator + "info.txt");
常用方法
// File类的获取功能
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数组
// File类的重命名功能
public boolean renameTo(File dest):把文件重命名为指定的文件路径
// 要想保证返回true,需要file1在硬盘中存在,file2不存在
// 相当于把文件移过去并重命名
file1.renameTo(file2);
// File类的判断功能
public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
// File类的创建功能
public boolean createNewFile():创建文件。若文件存在,则不创建,返回false
public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建
注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
// File类的删除功能
public boolean delete():删除文件或者文件夹删除
//注意事项:Java中的删除不走回收站。要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
流的分类
文本文件使用字符流,非文本文件使用字节流。
抽象基类 | 节点流(文件流) | 缓冲流(处理流的一种) |
---|---|---|
InputStream(字节流) | FileInputStream | BufferedInputStream |
OutputStream(字节流) | FileOutputStream | BufferedOutputStream |
Reader(字符流) | FileReader | BufferedReader |
Writer(字符流) | FileWriter | BufferedWriter |
节点流
FileReader
读入的文件一定要存在,否则会有异常。
// 1. 实例化File类对象,指明要操作的文件
File file = new File("hello.txt");
// 2. 提供具体的流
FileReader fr = new FileReader(file);
// 3. 数据读入
int date = fr.read(); // 返回读入的一个字符。如果到达末尾,返回-1
while (data != -1) {
sout((char) data);
data = fr.read();
}
// 简写
int data;
while ((data = fr.read()) != -1) {
sout((char) data);
}
// 4. 流的关闭操作
fr.close();
含异常处理的完整代码:
FileReader fr = null;
try {
File file = new File("hello.txt");
fr = new FileReader(file);
int data;
while ((data = fr.read()) != -1) {
sout((char) data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fr != null) fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
对read()
的升级:
char[] cbuf = new char[5];
//fr.read(cbuf); // 返回每次读入数组中字符的个数。如果到达末尾,返回-1
while ((len = fr.read(cbuf)) != -1) {
// 正确写法一
for (int i = 0; i < len; i++) { // 注意循环次数是len(如果字符串为hello123,则第二次读入后的cbuf变为123lo)
sout(cbuf[i]);
}
// 正确写法二
String str = new String(cbuf, 0, len);
sout(str);
}
FileWriter
File可以不存在,如果不存在,会自动创建文件。如果存在,如果流使用的构造器是FileWriter(file)或FileWriter(file, false)
则覆盖原文件,如果流使用的构造器是FileWriter(file, true)
则追加。
// 1. 提供File类对象
File file = new File("hello.txt");
// 2. 提供FileWriter
FileWriter fw = new FileWriter(file);
// 3. 写出操作
fw.write("I have a dream\n");
fw.write("aaabbb");
fw.write(cbuf, 0, len);
// 4. 流资源关闭
fw.close();
FileInputStream和FileOutputStream
// 省略了异常处理
// 1. 实例化File类对象,指明要操作的文件
File file1 = new File("hello1.jpg");
File file2 = new File("hello2.jpg");
// 2. 提供具体的流
FileInputStream fis = new FileInputStream(file1);
FileOutputStream fos = new FileOutputStream(file2);
// 3. 数据读入
byte[] buffer = new byte[5];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
// 4. 流的关闭操作
fos.close();
fis.close();
缓冲流
提高流的读取写入速度。内部提供了默认8192字节的缓冲区。
BufferInputStream和BufferOutputStream
// 1. 造文件
File srcFile = new File("test1.jpg");
File destFile = new File("test2.jpg");
// 2.1 造节点流
FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);
// 2.2 造缓冲流
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
// 3. 读取写入
byte[] buffer = new byte[10];
int len;
while((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
// 4. 资源关闭(先关闭外层的流,再关闭内层的流)
bos.close();
bis.close();
// 关闭外层流的同时,内层流也会自动关闭,所有以下代码可以省略
// fos.close();
// fis.close();
BufferedReader和BufferedWriter
BufferedReader br = new BufferedReader(new FileReader(new File("hello1.txt")));
BufferedWriter bw = new BufferedWriter(new FileWriter(new File("hello2.txt")));
// 方法一
char[] cbuf = new char[1024];
int len;
while ((len = br.read(cbuf)) != -1) {
bw.write(cbuf, 0, len);
// bw.flush();
}
// 方法二
String data;
while ((data = br.readLine()) != null) {
bw.write(data); // data不包括换行符
bw.newLine(); // 换行(或自己\n)
}
br.close();
bw.close();
转换流
字节和字符之间的转换。
InputStreamReader
:将字节的输入流转化为字符的输入流
OutputStreamWriter
:将字符的输出流转换为字节的输出流
FileInputStream fis = new FileInputStream("test1.txt");
FileOutputStream fos = new FileOutputStream("test2.txt");
// 根据文件的具体编码来解码
// InputStreamReader isr = new InputStreamReader(fis); // 默认解码
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
// 输出为指定编码
OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
char[] cbuf = new char[20];
int len;
while((len = isr.read(cbuf)) != -1) {
osw.write(cbuf, 0, len);
}
isr.close();
osw.close();
标准输入输出流
System.in
:标准输入流,默认从键盘输入
System.out
:标准输出流,默认从控制台输出
方法setIn(InputStream in)
和setOut(PrintStream out)
可以重新指定输入输出的流。
/*
System.in -> 转换流 -> BufferedReader.readLine()
*/
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedRead(isr);
while (true) {
String data = br.readLine();
if (data.equals("exit")) {
break;
}
sout(data.toUpperCase());
}
br.close();
打印流
PrintStream
和PrintWriter
:提供了一系列重载的print()
和println()
。
System.out
就是PrintStream
的对象。
FileOutputStream fos = new FileOutputStream(new File("hello.txt"));
// 创建打印流,true表示设置为自动刷新模式(写入换行符或'\n'时刷新缓冲区)
PrintStream ps = new PrintStream(fos, true);
System.setOut(ps); // 将标准输出流替换掉
for (int i = 0; i < 256; i++) {
System.out.print((char) i);
if (i % 50 == 0) sout();
}
ps.close();
数据流
DataInputStream
和DataOutputStream
:用于读取或写出基本数据类型的变量或字符串。
// 写
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
dos.writeUTF("一二三");
dos.flush();
dos.writeInt(123);
dos.flush();
dos.writeBoolean(true);
dos.flush(); // 刷新就会将内存中的数据写入文件
dos.close();
// 读(要按照写入的顺序读)
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
String name = dis.readUTF();
int age = dis.readInt();
boolean isMale = dis.readBoolean();
dis.close();
对象流
ObjectlnputStream
和OjbectOutputSteam
:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
- 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
- 反序列化:用ObjectlnputStream类读取基本类型数据或对象的机制
ObjectOutputStream和ObjectlnputStream不能序列化static和transient修饰的成员变量
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
oos.writeObject(new String("啦啦啦啦啦"));
oos.flush();
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileOutputStream("object.dat"));
Object obj = ois.readObject();
String str = (String) obj;
ois.close();
如果要序列化自定义类:
- 需要实现
Serializable
接口 - 提供
serialVersionUID
- 其内部所有属性也必须是可序列化的
class Person implements Serializable { // 标识接口(没有方法)
public static final long serialVersionUID = 4643165953L; // 提供这个值
}
随机存取文件流
RandomAccessFile
类:
RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了Datalnput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
RandomAccessFile 类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:
long getFilePointer()
:获取文件记录指针的当前位置void seek(long pos)
:将文件记录指针定位到 pos 位置
构造器
public RandomAccessFile(File file,String mode)
public RandomAccessFile(String name, String mode)
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
- r:以只读方式打开
- rw:打开以便读取和写入
- rwd:打开以便读取和写入;同步文件内容的更新
- rws:打开以便读取和写入;同步文件内容和元数据的更新
如果模式为只读r,则不会创建文件,而是会去读取一个已经存在的文件如果读取的文件不存在则会出现异常。如果模式为rw读写,如果文件不存在则会去创建文件,如果存在则不会创建。
RandomAccessFile 作为输出流时,写出到的文件如果不存在会自动创建。如果存在,则会默认从头覆盖。
RandomAccessFile raf1 = new RandomAccessFile(new File("hello.txt"), "r");
RandomAccessFile rafw = new RandomAccessFile("hello.txt", "rw");
// 示例1
byte[] buffer = new byte[1024];
int len;
while((len = raf1.read(buffer)) != -1) {
raf2.write(buffer, 0, len);
}
// 示例2
raf1.write("xyz".getBytes()); // 将开头三个字母覆盖
// 示例3
raf1.seek(3); // 将指针调到3的位置
raf1.write("xyz".getBytes());
raf1.close();
raf2.close();
ByteArrayOutputStream插入数据
网络编程
概念
通信要素:IP和端口号(0~65535)
IP和端口号的组合得出一个网络套接字Socket
InetAddress
代表IP。
InetAddress inet1 = InetAddress.getByName("192.168.10.14");
InetAddress inet2 = InetAddress.getByName("www.atguigu.com");
InetAddress inet3 = InetAddress.getLocalHost(); // 获取本机IP
sout(inet2.getHostName()); // 域名
sout(inet2.getHostAddress()); // IP地址
TCP
服务器客户端发送接收数据方法类似,参考TCPTEST
。
// 异常处理同IO
public void client() {
// 1. 创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("127.0.0.1");
Socket socket = new Socket(inet, 8899);
// 2. 获取一个输出流,用于输出数据
OutputStream os = socket.getOutputStream();
// 3. 写数据
os.write("你好,我是客户端".getBytes());
// socket.shutdownOutput(); 显示关闭输出
// 4. 资源关闭
os.close();
socket.close();
}
public void server() {
// 1. 创建服务器端的ServerSocket,指明自己的端口号
ServerSocket ss = new ServerSocket(8899);
// 2. 调用accept()表示接收来自于客户端的socket
Socket socket = ss.accept();
// 3. 获取输入流
InputStream is = socket.getInputStream();
// 4. 读取输入流的数据
/*byte[] buffer = new byte[20];
int len;
while ((len = is.read(buffer)) != -1) {
String str = new String(buffer, 0, len);
sout(str); // 可能会乱码
}*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[20];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
sout(baos.toString());
sout(socket.getInetAddress().getHostAddress()); // 数据发送方
// 5. 资源关闭
baos.close();
is.close();
socket.close();
ss.close();
}
UDP
public void sender() {
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();
}
public void receiver() {
DatagramSocket socket = new DatagramSocket(9090);
byte[] buffer = new byte[100];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
socket.receive(packet);
sout(new String(packet.getData(), 0, packet.getLength()));
socket.close();
}
URL编程
URL:统一资源定位符
URL url = new URL("http://localhost:8080/examples/a.txt");
/* URL的一系列方法
getProtocol
getPort
getPath
getFile
getQuery
*/
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.connect();
InputStream is = urlConnection.getInputStream();
// 省略流的相关操作
urlConnection.disconnect();
反射
Class类
将字节码文件加载到内存中叫做类的加载,加载到内存中的类称为运行时类,此运行时类就作为Class的一个实例。
加载到内存中的运行时类会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取Class实例的方式:
// 方法一:调用运行时类的属性
Class clazz1 = Person.class;
//或 Class<Person> clazz1 = Person.class;
// 方法二:通过运行时类的对象调用getClass方法
Person p1 = new Person();
Class clazz2 = p1.getClass();
// 方法三:调用Class的静态方法forName(String classPath)
Class clazz3 = Class.forName("com.atguigu.java.Person");
// Class clazz3 = Class.forName("java.lang.String");
// 方法四:使用类的加载器
ClassLoader classLoader = 当前类名.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
ClassLoader
// 对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
// 调用系统类加载器的getParent(): 获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.printIn(classLoader1);
// 调用扩展类加载器的getParent(): 无法获取引导类加载器
// 引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.printIn(classLoader2);
用ClassLoader
读取配置文件:
Properties pros = new Properties();
// 此时的文件默认在当前的module下。
// 读取配置文件的方式一
FileInputStream fis = new FileInputStream("jdbc.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);
创建运行时类的对象
newInstance()
:创建对应的运行时类的对象。内部调用了运行时类的空参构造器。
要想此方法正确执行:运行时类必须提供空参构造器,空参构造器的访问权限得够(通常为public)
在javabean中要求提供一个public的空参构造器:
- 便于通过反射创建运行时类的对象
- 便于子类继承此运行时类时,默认调用super()时保证父类有此构造器
Class clazz = Person.class;
Object obj = clazz.newInstance();
Person person = (Object) obj;
// 使用泛型
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
获取运行时类的完整结构
属性
Class clazz = Person.class;
// getFields() 获取当前运行时类及其父类中声明为public的属性
Field[] fields = clazz.getFields();
// getDeclaredFields() 获取当前运行时类中声明的所有属性(不包含父类)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f: declaredFields) {
// 权限修饰符
int modifier = f.getModefiers();
sout(Modifier.toString(modifier));
// 数据类型
Class type = f.getType();
sout(type);
// 变量名
String fName = f.getName();
sout(fName);
}
方法
Class clazz = Person.class;
// getMethods() 获取当前运行时类及其父类中声明为public的方法
Method[] methods = clazz.getMethods();
// getDeclaredMethods() 获取当前运行时类中声明的所有方法(不包含父类)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m: declaredMethods) {
// 注解(注解的@Retention为RUNTIME才会被获取)
Annotation[] annos = m.getAnnotations();
// 权限修饰符
sout(Modifier.toString(m.getModifiers()));
// 返回值类型
sout(m.getReturnType().getName());
// 方法名
sout(m.getName());
// 形参列表
Class[] parameterTypes = m.getParameterTypes();
if (!(parameterTypes == null || parameterTypes.length == 0)) {
for (int i = 0; i < parameterTypes.length; i++) {
sout(parameterTypes[i].getName() + "args_" + i);
}
}
// 抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
if (!(exceptionTypes == null || exceptionTypes.length ==0)) {
for (int i = 0; i < exceptionTypes.length; i++) {
sout(exceptionTypes[i].getName());
}
}
}
构造器
Class clazz = Person.class;
// 获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
// 获取当前运行时类中声明的所有构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
......
父类
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
Type genericSuperclass = clazz.getGenericSuperclass(); // 获取带泛型的父类
// 获取父类的泛型
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = paramType.getActualTypeArguments();
sout(actualTypeArguments[0].getTypeName());
sout((Class) actualTypeArguments[0].getName()); // 效果一样
接口
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces(); // 获取本类的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); // 获取父类的接口
包
Class clazz = Person.class;
Package pack = clazz.getPackage();
类的注解
Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
调用运行时类的指定结构
属性
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 获取指定变量名的属性
//Field id = clazz.getField("id"); // 只能获取public的
Field name = clazz.getDeclaredField("name");
name.setAccessible(true); // 保证当前属性可访问
// 设置属性的值 set(哪个对象, 设置为多少)
id.set(p, 1001);
// 获取属性的值 get(获取哪个对象的参数的值)
sout(id.get(p));
方法
非静态方法
Class clazz = Person.class;
// 创建运行时类的对象
Person p = (Person) clazz.newInstance();
// 获取指定的方法 getDeclaredMethod(方法名, 形参列表)
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
// invoke(调用者, 给形参赋值的实参),invoke方法的返回值即为调用方法的返回值
Object returnValue = show.invoke(p, "CHN");
String ret = (String) returnValue;
静态方法
Class clazz = Person.class;
Method show = clazz.getDeclaredMethod("show");
show.setAccessible(true);
show.invoke(Person.class或null)); // 没有返回值invoke就返回null
构造器
Class clazz = Person.class;
// 获取指定的构造器 getDeclaredConstructor(形参列表)
Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
// 调用构造器创建对象 newInstance(参数列表)
Person person = (Person) constructor.newInstance("Tom");
动态代理
interface Human {
String getBelief();
void eat(String food);
}
// 被代理类
class SuperMan implements Human {
@Override
public String getBelief() {
return "I believe I can fly";
}
@Override
public void eat(String food) {
sout("我喜欢吃" + food);
}
}
class ProxyFactory {
// 调用此方法,返回一个代理类对象(参数obj:被代理类对象)
public static Object getProxyInstance(Object 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时,就会自动调用如下方法
// 将被代理类要执行的方法a的功能声明在invoke中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throw Throwable {
// 代理类对象调用的方法,即被代理的方法
Object returnValue = method.invoke(obj, args);
return returnValue;
}
}
public class ProxyTest {
main() {
SuperMan superMan = new Superman();
// 创建代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
// 当通过代理类的对象调用方法时,会自动调用被代理类中同名的方法
sout(proxyInstance.getBelief());
proxyInstance.eat("麻辣烫");
}
}