2.常用类
String类
String的实例化方式:
方式一:通过字面量定义的方式(直接赋值)
方式二:通过new +构造器的方式
-
String是一个final类,代表不可变的字符序列(不可变性: String类中的值一旦声明,便不可修改,因为每个字符串都是一个对象,如果改变,只是对象名称指向了另一个内存空间而已)。不可被继承。
-
字符串是常量,用" "引起来表示。它们的值在创建之后不能更改。(String对象是不可改变的)
-
String对象的字符内容是存储在一个字符数组value[]中的。
-
String实现了Serializable接口:表示字符串是支持序列化的。
实现了Comparable接口:表示String可以比较大小
-
不可变性体现:
- 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
- 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 当调用String 的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
特点:
- 通过字面量的方式(直接赋值不是new)给一个字符串赋值,此时的字符串值声明在字符串常量池(即方法区)中。
- 字符串常量池中是不会存储相同内容的字符串的。
- 用==比较的是地址值
- 用equal方法比较的是内容,String已经重写过此方法了即比较的是内容值,其他一般需要重写此方法才有这样的效果。
String不同拼接操作结论:
- 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。 (final修饰的变量是常量)
- 只要其中有一个是变量,结果就在堆中
- 如果拼接的结果调用intern()方法,返回值就在常量池中
直接赋值和new对象赋值的区别?
new对象是在堆中产生一个新的地址指向value引用类型,此引用类型指向的是常量池中的字符串地址值。即new一个对象就会产生一个新的地址,但是其实本质上还是指向共同的地址(常量池中的字符串地址值),间接指向。
- 字符串常量存储在字符串常量池,目的是共享。
- 字符串非常量对象存储在堆中。
new方式创建对象,在内存中创建了几个对象?
两个。一个是堆空间中new结构, 另一个是char[]对应的常量池中的数据:“abc”
不可变性
对于String的不可变性 有两种情况
第一种:
解释: 如图 当String作为形参传递到方法里的时候,实际上传递的是str引用的拷贝,改变的是str引用的拷贝,而后方法结束,形参str被销毁, 原str的引用保持不变,还是指向原常量池中的1234 这个方法输出的是1234.
第二种:
对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:
打印结果为:
s = ABCabc
s = 123456
解释: 首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。
常用方法
-
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
-
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
-
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
-
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
-
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
-
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
-
**int lastIndexOf(String str, int fromIndex):**返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String与基本数据类型、包装类之间的转换。
- string -->基本数据类型、包装类:调用包装类的静态方法: parseXxx( str)
- 基本数据类型、包装类–>String:调用String重载的valueof(xxx)
String与字符数组(char [])转换
- String–> char []: 调用String的toCharArray()
- char[] --> String:调用String的构造器
String与字节数组(byte [])转换
- String --> byte []: 调用String的getBytes()
- byte [] --> String:调用String的构造器
编码:字符串 --> 字节 (看得懂---->看不懂的二进制数据)
解码:编码的逆过程: 字节–> 字符串
StringBuffer和StringBuilder
源码分析
String s1 = new String();//char[] value = new char[0];
String s2 = new String("abc");//char[] value = new char[]{'a','b','c'};
StringBuffer st1 = new StringBuffer();//char[] value = new char[16];底层创建了一个长度为16的字符数组
st1.append('a');//value[0] = 'a';
st1.append('b');//value[1] = 'b';
StringBuffer st1 = new StringBuffer("abc");//char[] value = new char["abc".length+16];底层创建了一个str长度+16的长度的字符数组
问题1.System.out.println(sb2.Length());//3-----是根据append以及刚开始传入的数据获取长度的
问题2.扩容问题: 如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。
注意:开发中建议使用StringBuffer(指定长度)这个构造器。
常用方法
增删改查插入长度遍历
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() :把当前字符序列逆转
public int indexOf(String str)
public String substring(int start,int end) 左闭右开
public int length()
public char charAt(int n )
public void setCharAt(int n ,char ch)
- 当append和insert时,如果原来value数组长度不够,可扩容。
- 如上这些方法支持方法链操作。
String-StringBuffer-StringBuilder 三者异同
-
string:不可变的字符序列; 底层使用char[]存储,创的数组长度是根据传入数据的长度来控制的。
-
stringBuffer:可变的字符序列; 线程安全的(因为此类方法都是同步方法即关于线程的安全问题), **效率低;**底层使用char[]存储,创的数组长度本来默认的就有一定长度所以可变。
-
stringBuilder:可变的字符序列; jdk5.0新增的,线程不安全的,**效率高; **底层使用char[]存储
-
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且
提供相关功能的方法也一样
-
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder
会改变其值
System获取时间戳
System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
jkd8之前的Date类
java.util.Date类
- 两个构造器
- new Date() – 创建一个对应当前时间的Date对象
- new Date(long date) --创建指定毫秒数的Date对象
- 两个方法的使用
- toString(): 显示当前的年月日时分秒
- getTime(): 获取当前Date对象对应的毫秒数 即时间戳
java.sql.Date类
对应数据库中的日期类型的变量
- 一个构造器
- new Date(long date) --创建指定毫秒数的Date对象
- 将java.util.Date对象转换为java.sql.Date对象
- Date date6 = new Date();
- java.sql.Date date7 = new java.sql.Date(date6.getTime());
SimpleDateFormat
对日期Date类的格式化和解析
- 格式化: 日期---->字符串
- 解析:格式化的逆过程,字符串----> 日期
//格式化
SimpleDateFormat sdf = new SimpleDateFormat();//实例化 默认无参构造器
Date d2 = new Date();
String s1 = sdf.format(d2);//格式化
System.out.println(s1);
//解析
String s2 = "2022/5/20 下午6:20";
try {
Date d3 = sdf.parse(s2);
System.out.println(d3);
} catch (ParseException e) {
e.printStackTrace();
}
带参构造器 即更改时间的模板
Date d2 = new Date();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");//格式化
String s1 = sdf1.format(d2);
System.out.println(s1);
Calendar日历类(抽象类)
- 实例化
- 方式一: 创建其子类(Gregoriancalendar)的对象
- 方式二: 调用其静态方法getInstance()
Java比较器
Java实现对象排序的方式有两种:
-
自然排序:java.lang.Comparable
-
定制排序:java.util.Comparator
Comparable接口
-
Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
-
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大 于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零
(返回正整数即把this对象放在后面,返回负整数即把this对象放在前面,顺序按从左往右排序,后面是右边的意思,前面是左边的意思;所以this大于形参对象obj,则返回正整数即从小到大排序;this小于形参对象obj,则返回正整数即从大到小排序)
//先按年龄从小到大 如果相等按名字从小到大 @Override public int compareTo(Object o) { Person o1 = (Person)o; if(this.age<o1.age) return -1; if(this.age>o1.age) return 1; if(this.name.compareTo(o1.name)>0) return 1; if(this.name.compareTo(o1.name)<0) return -1; return 0; }
-
实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
-
对于类 C 的每一个 e1 和 e2 来说,当且仅当 e1.compareTo(e2) == 0 与e1.equals(e2) 具有相同的 boolean 值时,类 C 的自然排序才叫做与 equals 一致。建议(虽然不是必需的)最好使自然排序与 equals 一致。
注意:1. String、包装类重写compareTo()方法以后,进行了从小到大的排列
- 自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,**重写compareTo(obj)**方法,在compareTo(obj)方法中指明如何排序
Comparator接口
- **当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,**强行对多个对象进行整体排序的比较。
- 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2。
- 可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制。
- 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。
两个接口的区别
-
Comparable接口的方式一旦确定,保证Comparable接口实现类的对象在任何位置都可以比较大小。因为此接口已经写在了实现类中了实现了 compareTo(Object obj) 方法。所以是可以随时调用的。
-
Comparator接口属于临时性的比较。即用的时候重写的方法。
Arrays.sort(arr, new Comparator<String>() { @Override public int compare(String o1, String o2) { if(o1.compareTo(o2)>=0) return -1; else return 1; } });
System类
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
-
由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
-
成员变量 System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
-
成员方法
-
native long currentTimeMillis()
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时
间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
-
void exit(int status)
该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表
异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
-
枚举类与注解
-
类的对象只有有限个,确定的。
-
当需要定义一组常量时,强烈建议使用枚举类
-
如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
一:自定义枚举类
class Season{
//自定义枚举类的属性 最终类不可改变
private final String seasonName;
private final String seasonDesc;
private Season(String seasonName,String seasonDesc){ //私有构造器
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//有限个数
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "冰天雪地");
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
二: enum关键字定义枚举类
说明:1.使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
2.枚举类的构造器只能使用 private 权限修饰符
- 第一步需要先把所有枚举类对象写上,多个对象之间用逗号隔开,末尾对象分号结束列出的实例系统会自动添加 public static final 修饰
- 必须在枚举类的第一行声明枚举类对象
enum Season1 {
//1.提供当前枚举类的对象,多个对象之间用逗号隔开,末尾对象分号结束。
SPRING("春天","春暖花开"),
SUMMER("夏天", "夏日炎炎"),
AUTUMN ("秋天", "秋高气爽"),
WINTER("冬天", "冰天雪地");
//自定义枚举类的属性 最终类不可改变
private final String seasonName;
private final String seasonDesc;
private Season1(String seasonName,String seasonDesc){ //私有构造器
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//toString一般不需要重写了 根据实际情况是否需求
}
常用方法
-
values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值
Season1[] values = Season1.values(); for(Season1 i:values){ System.out.println(i); }
-
valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
-
toString():返回当前枚举类对象常量的名称
枚举类实现接口
-
和普通 Java 类一样,枚举类可以实现一个或多个接口
-
若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
-
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
interface int1{ void show(); } enum Season1 implements int1{ //1.提供当前枚举类的对象,多个对象之间用逗号隔开,末尾对象分号结束。 SPRING("春天","春暖花开"){ @Override public void show() { System.out.println("春"); } }, SUMMER("夏天", "夏日炎炎"){ @Override public void show() { System.out.println("夏"); } }, AUTUMN ("秋天", "秋高气爽"){ @Override public void show() { System.out.println("秋"); } }, WINTER("冬天", "冰天雪地"){ @Override public void show() { System.out.println("冬"); } }; //自定义枚举类的属性 最终类不可改变 private final String seasonName; private final String seasonDesc; private Season1(String seasonName,String seasonDesc){ //私有构造器 this.seasonName = seasonName; this.seasonDesc = seasonDesc; } public String getSeasonName() { return seasonName; } public String getSeasonDesc() { return seasonDesc; } }
注解(Annotation)
框架 = 注解 + 反射 + 设计模式。
说明:
-
Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
-
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方 法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中。
-
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
集合
- Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中
- 集合、数组都是对多个数据进行存储操作的结构,简称java容器。
集合存储的优点:解决数组存储数据方面的弊端。
Collection接口
- Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
- JDK不提供此接口的任何直接实现,而是提供更具体的子接口(如:Set和List)实现。
- 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 JDK 5.0 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
注意:
-
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类要重写equals()。
-
将数组–>集合转换:调用Arrays的静态方法----Arrays.asList(传入的数组);基本数据类型数组必须改为包装类型数组。
-
迭代器遍历 //C语言中的指针 跟C++中的迭代器类似
Iterator it = coll.iterator();//指针指向在集合的第一个位置的上面 while(it.hasNext()){ Object n1 = it.next(); System.out.println(n1); //next():①指针下移 ②将下移以后集合位置上的元素返回 //System.out.println(it.next()); }
-
foreach循环----可以用来遍历集合和数组
//for(集合元素的类型 局部变量:集合对象) //内部仍然调用了迭代器 for(Object obj : coll){ System.out.println(obj); }
List接口
常用方法
ArrayList、LinkedList、Vector三者的异同!
- 相同点: 三个类都是实现了List接口,存储数据的特点相同: 存储有序的、可重复的数据
- 不同点:
- ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
- LinkedList: 对于频繁的插入、删除操作,使用此类效率比ArrayList高; 底层使用双向链表存储(first、last)
- Vector: 作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
Set接口
解释:存储无序的、不可重复的数据 即相当于数学中的集合
- 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加。而是根据数据的哈希值
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即: 相同的元素只能添加一个.
实现类
- HashSet:作为Set接口的主要实现类 ; 线程不安全的; 可以存储null值
- LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
- TreeSet: 可以按照添加对象的指定属性,进行排序
HashSet
LinkedHashSet
根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。对于频繁的遍历操作,LinkedHashSet效率高于HashSet。
TreeSet
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
注意:向Treeset中添加的数据,要求是相同类的对象。TreeSet底层使用红黑树结构存储数据
自然排序
比较两个对象是否相同的标准为: compareTo()返回0. 不再是equals(). 需要实现comparable接口
注意:添加元素的时候会自动调用类的compareTo()实现方法 即排序放法
定制排序Comparator接口
这个时候new TreeSet需要传入Comparator接口一个实例,如果用无参构造器即默认自然排序
Comparator com = new Comparator(){
@Override
public int compare(Object o1, Object o2) {
return 0;
}
};
TreeSet treeSet = new TreeSet(com);//传参
Map接口
- 一个键值对:key-value构成了一个Entry对象。
- Map中的entry:无序的、不可重复的,使用Set存储所有的entry
Map<String, Integer> map1 = new HashMap<>();
map1.put("Tom", 18);
map1.put("John", 25);
map1.put("Lilei", 12);
map1.put("Jim", 28);
//通过entrySet() 获得所有键值对
Set<Map.Entry<String, Integer>> entries = map1.entrySet();
Iterator<Map.Entry<String, Integer>> it = entries.iterator();
while(it.hasNext()){
Map.Entry<String, Integer> n1 = it.next();
String key = n1.getKey();
Integer value = n1.getValue();
System.out.println(key+"--"+value);
}
Properties
Collections工具类
List、Set、Map三者对比
泛型
解释:所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。泛型的类型必须是类,不能是基本数据类型
注意:如果实例化的时候没有指明泛型的类型,默认为java.lang.Object类型
-
在集合中没有泛型时:
-
任何类型都可以添加到集合中:类型不安全
-
读取出来的对象需要强转(因为是Object类型所以需要强转):繁琐 —可能有ClassCastException 类型转换异常
-
-
在集合中有泛型时:
- 只有指定类型才可以添加到集合中:类型安全
- 读取出来的对象不需要强转:便捷
遍历方法
ArrayList<String> a1= new ArrayList<>();
a1.add("abc");
a1.add("def");
a1.add("efg");
//方法一
for (int i = 0; i < a1.size(); i++) {
System.out.println(a1.get(i));
}
//增强for
for (Object a: a1) {
System.out.println(a);
}
//迭代器
Iterator<String> t1 = a1.iterator();
while(t1.hasNext()){
System.out.println(t1.next());
}
自定义泛型结构
泛型类、泛型接口
- 如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为0bject类型
- 如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
- 由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型.
泛型方法
[public] [static] 返回值类型 方法名(T 参数列表)
在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E i:arr){
list.add(i);
}
return list;
}
- 泛型方法在调用时,指明泛型参数的类型
- 泛型方法是可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
泛型在继承方面的体现
Object obj = null;
String str = null;
obj = str;
List<Object> list1 = null;
List<String> list2 = null;
//此时的list1和list2的类型不具有子父类关系
list1 = list2;//报红
总结: 类A是类B的父类,G和G二者不具备子父类关系,二者是并列关系。
类A是类B的父类,A
List<String> list1 = null;
ArrayList<String> list2 = null;
list1 = list2;//不报红
通配符的使用
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//这时候list相当于list1和list2的公共父类了
总结:类A是类B的父类,G和G是没有关系的,二者共同的父类:G<?>
- 对于List<?>就不能向其内部添加数据了,除了添加null之外。
- 允许读取数据get(int index),读取的数据类型为Object
注意点:
- 编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
- 编译错误:不能用在泛型类的声明上
- 编译错误:不能用在创建对象上,右边属于创建集合对象
有限制条件的通配符
- 通配符指定上限:上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
- 通配符指定下限:下限super:使用时指定的类型不能小于操作的类,即>=
List<? extends String> list = null;
List<? super String> listt = null;
List<Object> list1 = null;
List<String> list2 = null;
list = list1;//报红!!!
listt = list1;
list = list2;
listt = list1;
IO流
File类
此类对象,代表一个文件或一个文件目录(俗称:文件夹)----声明在java.io包下
- java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
- File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对 象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
相对路径:相较于某个路径下,指明的路径。
绝对路径:包含盘符在内的文件或文件目录的路径
IDEA:
- @Test测试(单元测试)里面的相对路径是相较于当前Module
- Main方法里面的相对路径是相较于当前工程
常用构造器
常用方法
注意: 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".
流的分类
节点流和处理流
节点流:直接从数据源或目的地读写数据。
处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。
注意:1. read()的理解:返回读入的一个字符,如果达到文件末尾,返回-1
- 异常的处理: 为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
- 读入的文件一定要存在,否则就会报FileNotFoundException
- 对于文本文件( .txt,.java,.c,.cpp),使用字符流处理
- 对于非文本文件( .jpg.mp3 .mp4,.avi…)使用字节流处理
缓冲流(处理流)
- 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
- 缓冲流要“套接”在相应的节点流之上(构造器传入节点流),根据数据操作单位可以把缓冲流分为:BufferedInputStream 和 BufferedOutputStream、BufferedReader 和 BufferedWriter
@Test
public void test1(){
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("hello.txt")));
bw = new BufferedWriter(new FileWriter(new File("hello1.txt")));
char[] buf = new char[1024];
int len;
while((len = br.read(buf))!=-1){
bw.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(br!=null) br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(bw!=null) bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换流(处理流)
转换流: 属于字符流(看后缀)
作用: 转换流提供了在字节流和字符流之间的转换n
- InputStreamReader(解码):将一个字节的输入流转换为字符的输入流
- OutputStreamWritter(编码):将一个字符的输出流转换为字节的输出流
标准输入输出流
流的操作
- 提供File类的对象,指明文件的路径
- 提供流的对象(实例化)
- 通过流的对象对数据操作
- 资源的关闭!!!
反射
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
关于java.Lang.CLass类的理解
-
类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。(类本身也是对象) -
Class的实例就对应着一个运行时类。
-
加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类。
获取Class的实例的方式
-
调用运行时类的属性:.class
Class<Person> 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.school.java.Person"); System.out.println(clazz3); //体现反射的动态性 运行时才开始调用 //创建运行时类的对象 //Object obj = clazz3.newInstance();//过时了 Object obj = clazz3.getDeclaredConstructor().newInstance(); //对应的运行时类必须要有空参的构造器 还有权限问题!!
哪些类型可以有Class对象
(1)class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void
ClassLoader类加载器
类加载器的作用:
- **类加载的作用:**将class文件字节码内容加载到内存中,并将这些静态数据转换成方
法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为
方法区中类数据的访问入口。
- **类缓存:**标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器
中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
注意:
- 在javabean中要求提供一个public的空参构造器。原因:1.便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
获取运行时类的完整结构
操作运行时类中的指定的属性
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person p1 = clazz.newInstance();
//1.获取指定变量名的属性
Field name = clazz.getDeclaredField("name");
//2.保证当前属性是可访问的
name.setAccessible(true);
//3.获取、设置当前对象属性的值
name.set(p1, "Tom");
System.out.println(name.get(p1));
操作运行时类中的指定的方法
非静态方法:
Class<Person> clazz = Person.class;
//创建运行时类的对象
Person p = clazz.newInstance();
//1.获取指定的某个方法
Method show = clazz.getDeclaredMethod("show", String.class);
//2.保证当前属性是可访问的
show.setAccessible(true);
//3.invoke():参数1:方法的调用者 参数2:给方法形参复制的实参
//invoke()的返回值即为对应类中调用的方法的返回值。
show.invoke(p, "CHN");//如果有返回值的话给一个变量接收
//比如Object obj = show.invoke(p,"CHN");
静态方法:
Class<Person> clazz = Person.class;
Method display = clazz.getDeclaredMethod("display");
display.setAccessible(true);
display.invoke(Person.class);
读取配置文件
FileInputStream fis = null;
Connection conn = null;
try {
Properties pros = new Properties();
//方式一
fis = new FileInputStream("jdbc.properties");//此时文件默认在当前的module下
//方式二
//fis = new FileInputStream("src\\jdbc.properties");
pros.load(fis);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
Class.forName(driverClass);
conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(fis!=null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(conn!=null) conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
网络编程
通信双方地址
IP
分类1:IPV4和IPV6
IPV4: 4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
IPV6: 128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984端口号
IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用
端口号
端口号标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号
被规定为一个 16 位的整数 0~65535。
端口分类:
**公认端口:**0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
**注册端口:**1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
动态私有端口:49152~65535。
端口号与IP地址的组合得出一个网络套接字:Socket
网络通信协议
TCP/IP协议(应用层、传输层、网络层、数据链路层)
TCP 三次握手 四次挥手
TCP:打电话 UDP:发短信、发电报
TCP客户端和服务器端
客户端:
- 自定义
- 浏览器
服务端:
- 自定义
- Tomcat服务器
//客户端
Socket socket = null;
OutputStream os = null;
try {//1.创建Socket对象,指明服务器端的ip和端口号
InetAddress inet = InetAddress.getByName("117.159.17.195");
socket = new Socket(inet, 8899);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写出数据的操作
os.write("你好,我是客户端1号".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.资源的关闭
try {
if(os!=null) os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket!=null) socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//服务器端
Socket socket = null;
InputStream is = null;
try {
//1.创建服务器端的ServerSocket,指明自己的端口号
ServerSocket ss = new ServerSocket(8899);
//2.调用accept()表示接受来自于客户端的socket
socket = ss.accept();
//3.获取输入流
is = socket.getInputStream();
//4.读取输入流中的数据
byte[] buff = new byte[1024];
int len;
while((len = is.read(buff))!=-1){
String s = new String(buff, 0, len);
System.out.print(s);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源
try {
if(is!=null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(socket!=null) socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
URL
Lambda表达式
这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。
它将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的参数列表
**右侧:**指定了 Lambda体,是抽象方法的实现逻辑(重写抽象方法的方法体),也即Lambda 表达式要执行的功能
//匿名内部类转换
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱中国!");
}
};
r1.run();
//Lambda
Runnable r2 =() -> System.out.println("我爱你!");
r2.run();
Comparator<Integer> c1 = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
int n1 = c1.compare(31, 24);
System.out.println(n1);
//Lambda
Comparator<Integer> c2 = (o1,o2) -> Integer.compare(o1, o2);
int n2 = c2.compare(12, 24);
System.out.println(n2);
//方法引用
Comparator<Integer> c3 = Integer::compare;
int n3 = c3.compare(55, 24);
System.out.println(n3);
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。