目录
String
String三种常见创建方式:
- String 变量名 = "";//使用常量串
- String 变量名 = new String("");//new String对象
- char[] array = {"","",""}; String 变量名 = new String(array) //使用字符数组
String是一个类,所以String是一个引用对象,变量名存储的不是值而是地址。
底层由private final修饰的字符数组。
常用的String方法:
方法 | 用法 |
int length() | 求字符串长度 |
boolean isEmpty() | 判断是否为空 |
boolean equals(str) | 判断字符串内容是否一致 |
int compareTo(str) | 比较字符串 |
char charAt(int index) | 返回index位置上字符,如果index为负数或者越界 |
int indexOf(int i) int indexOf(int i,int fromIndex) | 返回i第一次出现的位置,没有返回-1 从fromIndex开始返回i第一次出现的位置,没有返回-1 也可查找字符串,同int用法 |
toLowerCase/toUpperCase | 大小写转换 |
str.replace('','') | 替换 |
String[] split("分隔符") | 拆分字符串,获得分隔符两边的字符串,不包含分隔符 |
subString(int index1,int index2) subString(int index) | 截取,从index1开始,到index2结束,左闭右开。 从下标开始截取一直到字符末尾 |
trim() | 删除字符串开头和结尾的空白字符(空格, 换行, 制表符等). |
compare方法:与equals返回true/false不同,compare返回一个int值
- 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
- 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值
String.java是这样重写compareTo函数的:
public int compareTo(String anotherString) {
int len1 = value.length;//本字符串的长度
int len2 = anotherString.value.length;//另外一个字符串长度
int lim = Math.min(len1, len2);//去最小值
char v1[] = value;//将字符串转化为数组
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;//返回的是首个不同字符的ASCII相减值
}
k++;
}
return len1 - len2;
}
equals和==的区别
==:比较基本数据类型时,比较的是值;比较引用类型时,比较的是地址。
equals:Object的equals()底层是用==写的,所以比较的也是地址;但是,重写的equals()比较的是值。
Sring类本身被final修饰,而Sring类里面的字符数组value,是被private final修饰的
final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。
我们常说的String不能被修改是指String类里面的value数组不能更改,而不能更改的原因并不是因为final,而是因为private,这是一个私有变量,要想更改必须通过调用set方法,但是原生String类中并没有提供setvalue 方法,所以没有办法更改。
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。Java中的StringBuffer和StringBuilder是用于处理字符串的可变对象;这两种每次操作不会产生新的String对象。
Stringbuilder(重要)
可变字符串,效率高,线程不安全。
方法 | 用法 |
StringBuilder append(任意类型) | 添加字符串 |
StringBuilder reverse() | 字符串反转 |
int length() | 获取长度 |
String toString() | 变成字符串 |
常在字符串拼接、反转时使用
Stringbuffer
可变字符串,效率低,线程安全。多线程操作大量数据,用StringBuffer。
Stringjoiner
拼接字符串时可以设置起始/结束/间隔符号,方便拼接
两种构造方法:new StringJoiner(间隔符号),new StringJoiner(间隔符号,开始符号,结束符号)
集合
数组长度固定,集合大小可以变。数组可以存储基本数据类型和引用类型,集合中存储引用数据类型(存储的为对象的内存地址)。数组中只能存储同一种类型成员集合中可以存储不同类型数据(一般情况下也只存储同一种类型的数据)
单列集合collection
List
List存储有序(插入顺序和取出顺序一致),可重复数据,有下标,下标从0开始
ArrayList
常用方法:
方法名 | 说明 |
boolean add(E e) | 添加元素,成功返回true |
boolean remove(E e) | 删除指定元素,成功返回true |
E remove(int index) | 删除指定索引的元素,返回被删除元素 |
E set(int index,E e) | 修改指定索引元素,返回原来的元素 |
E get(int index) | 获取指定索引元素 |
int size() | 获取集合长度 |
底层原理:
线程不安全
LinkedList
底层是双链表,查询慢、增删快。
Set
存储无序,不可重复数据,无下标
Set接口继承了Collection接口,含有许多常用的方法。
int size();返回集合的长度
boolean isEmpty();判断集合是否为空
boolean contains(Object o);是否包含某个值
boolean add(E e);添加元素
boolean remove(Object o);删除元素
HashSet
无序,不重复,无索引
HashSet的底层是通过HashMap实现。
Cloneable:实现了clone()方法可以实现克隆功能
Serializable:表示可以被序列化传输。
TreeSet
可排序,不重复,无索引
它继承了AbstractSet抽象类,实现了NavigableSet,Cloneable,Serializable接口。它是非线程安全的,TreeSet是基于TreeMap实现的
TreeMap是通过红黑树实现的。
hashSet去重的方法是hashcode和equals方法判断相同则覆盖,TreeSet是通过compareTo方法的返回值来判断是否相同,如果返回值为0则认定是重复元素
LinkedHashSet
有序,不重复,无索引
LinkedHashSet是hashSet子类,是一个哈希表和链表的结合,且是一个双向链表
并且linkedHashSet是一个非线程安全的集合。如果有多个线程同时访问当前linkedhashset集合容器,并且有一个线程对当前容器中的元素做了修改,那么必须要在外部实现同步保证数据的准确性。
LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承于 HashSet,其所有的方法操作上又与 HashSet 相同
treeSet和hashSet区别
1、TreeSet 是二叉树(红黑树)实现的,Treeset中的数据是自动排好序的,不允许放入null值。
2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复。
3、HashSet要求放入的对象实现HashCode()和equals()方法,TreeSet要求放入的对象继承Comparable接口并实现compareTo方法或者在建TreeMap对象时,传入一个Comparator接口,并实现里面的compare方法
双列集合map
存储一 一对应的键值对,键+值称为键值对,即键值对对象(Entry对象)。
特点:键不能重复,值可以重复;键和值是一一对应的
方法名 | 说明 |
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除元素 |
void clear() | 移除所有键值对 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断是否为空 |
int size() | 获取长度,即键值对个数 |
添加时,如果键不存在则是添加;键存在则是覆盖
HashMap
hashMap特点由键的特点决定,键是无序,不重复,无索引的(set集合特征)
底层和hashSet一样,哈希表结构。通过hashCode和equals方法保证键的唯一。如果键存储自定义对象,需重写这两个方法。
TreeMap
可排序,不重复,无索引
两种都写以第二种为准
LinkedHashMap
键是有序,不重复,无索引
不可变集合
在List、Map、Set接口中,都存在静态的of方法,可以获取一个不可变集合。不能添加、删除、修改数据。它的参数是有上限的,最多20个参数,10个键值对。创建多个键值对可以用ofEntries(),jdk 10以后可以通过copyOf()创建不可变集合
可变参数
格式:类型... 变量名<=>数据类型[] 变量名
要求:形参中可变列表只能写一个,且必须在最后。
多态
同类型对象,表现出不同形态
父类引用指向子类对象 -> 父类类型 对象名 = 子类对象
特点:
变量调用:编译看左边,运行也看左边
原因:子类继承父类成员变量,根据数据类型确定调用的父类成员变量
方法调用:编译看左边,运行看右边
原因:子类重写父类方法,在虚方法表中覆盖了父类方法
多态前提:有继承关系,有父类引用指向子类对象
好处:使用父类类型作为参数,可以接受所有子类对象,体现多态的拓展性和便利。实现解耦合,便于扩展维护
弊端:无法访问子类的特有功能,必须强制类型转换,才能使用
异常
异常的种类
throwable 下的error和exception两种
Error一般是比较严重的问题(比如:java内部系统错误,资源耗尽等),所以我们一般不对Error进行异常处理,通常要对我们的代码逻辑进行思考改进。
exception也分两类
编译时异常,运行时异常。unchecked exception包括运行时异常和error类,其他所有异常称为检查(checked)异常。
一些常见的异常:
ClassCastException
//类型转换异常,IndexOutOfBoundsException
//数组越界异常
NullPointerException
//空指针,ArrayStoreException
//数组存储异常
NumberFormatException
//数字格式化异常,ArithmeticException
//数学运算异常
NoSuchFieldException
//反射异常,没有对应的字段,ClassNotFoundException
//类没有找到异常
IllegalAccessException
//安全权限异常
异常处理
try-catch:
try {
//可能出现异常的代码
} catch (异常类型 对象) {
//异常执行代码
}finally {
//最后执行的部分
}
finally部分无论是否出现异常都会执行,除非之前代码有system.exit(0)退出jvm或使用了Thread.stop()关闭线程。return不会影响finally的执行
throws抛出异常
使用throws关键字在类名后面抛出异常类型可以多个
格式:权限修饰符 返回值类型 类名 throws 异常类型1,异常类型2{}
注意:需要自己处理的异常千万别抛出,自己处理不了的问题一定要抛出去,若自己能处理的异常一直使用throws抛出的话,最终会交给Java虚拟机处理该异常,一旦出错,会直接将错误抛到页面或客户端,会造成用户体验性不好或者代码泄露。
throws和throw区别
throw是方法内部将异常对象创建出来的;
throws是方法声明时,将异常抛出给调用者的;
抽象类
由abstract修饰的类,抽象类有构造方法但不能被实例化,抽象类中可以有抽象方法(抽象类可以没有抽象方法,但有抽象方法的类一定是抽象类),也可以有非抽象方法。其他类继承抽象类重写抽象方法,可以不重写,但要变成抽象类。
接口
由interface修饰的特殊抽象类(没有class修饰),没有构造方法。接口的方法中只能是抽象方法、静态方法(此时要有方法体,static修饰)、默认方法(此时要有方法体,default修饰),后两个是jdk8新增的。
默认方法的使用场景包括:
- 为接口添加新的方法,而不会破坏已有代码的兼容性。
- 允许接口提供默认实现,从而减少实现类的工作量。
静态方法的使用场景包括:提供与接口相关的工具方法,这些方法可以在接口中定义为静态方法。
默认方法可以解决接口升级的问题,使得我们可以在不破坏已有代码的情况下向接口中添加新的方法。静态方法可以定义通用的工具方法,使得我们可以在不同的实现类中重复使用这些方法。
接口和其他类不一样,接口可以多继承
抽象类和接口区别:
抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对类行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是 "有没有"的关系。
内部类
类的五大成员:属性,方法,构造方法,代码块,内部类
定义:在一个类里定义的类被叫做内部类
特点:内部类可以访问外部类的成员,包括私有。外部类必须创建对象才能访问内部类
分类:成员内部类,静态内部类,局部内部类,匿名内部类
成员内部类:写在外部类的成员位置,成员内部类可以被修饰符修饰
获取成员内部类对象:1.通过外部类方法 2.外部类名.内部类名 对象名 = 外部类对象.内部类对象
方式一可以获取被private修饰的内部类,外部类方法返回一个内部类对象
如果外部类的成员变量和内部类成员变量重名,Outer.this.变量名获取外部类成员变量
静态内部类:由static修饰的内部类,只能访问外部类的静态变量和静态方法,访问非静态需创建对象。创建对象格式:外部类名.内部类名 对象名 = new 外部类名.内部类名。
调用静态内部类方法:非静态:使用对象调用;静态:外部类名.内部类名.方法名()。
局部内部类:定义在方法里的内部类,类似局部变量
外界无法直接访问,需要在方法里创建对象,可以直接访问外部类成员和方法内的局部变量
匿名内部类
没有名字的内部类,一般只使用一次才这样写。可以在成员位置/局部位置
格式:new 类名或接口名{重写方法},"{}"包括的才是匿名内部类,类名或接口名是匿名内部类要继承或实现的。
lambda表达式
函数式编程
是一种思想,忽略面向对象的复杂语法,强调做什么,而不是谁去做。lambda表达式就是这思想的体现。
函数式接口
有且只有一个抽象方法的接口,接口上方可以添加@functionalInterface 注解
lambda表达式
是对匿名内部类的简化,且只能简化函数式接口的匿名内部类写法。把函数作为一个方法的参数。
格式:(参数类型1 变量1,参数类型2 变量2)->{方法体}
省略写法:参数类型可以省略;如果只有一个形参,()可以省略;方法体只有一行,{}、分号、return可以同时省略;
双冒号(::)
Java 8中被用作方法引用,方法引用是与lambda表达式相关的一个重要特性。方法给接口提供的参数和他接收的返回,和你现有某个实现完全一致,就可以进一步进行简化,称为方法引用。使用lambda表达式会创建匿名方法, 但有时候需要使用一个lambda表达式只调用一个已经存在的方法(不做其它)。
语法(类名/类实例名::方法名):
- 静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
- 对象的实例方法引用语法:instancename::methodname 例如:System.out::println
- 对象的超类方法引用语法: super::methodname
- 类构造器引用语法: classname::new 例如:ArrayList::new
- 数组构造器引用语法: typename[]::new 例如: String[]:new
例子:
forEach:
//初始版本
for (Dog dog : dogs) {
System.out.println(dog);
}
//实现 new Consumer<?> 接口的匿名实现类,重写accept抽象方法
dogs.forEach(new Consumer<Dog>() {
@Override
public void accept(Dog dog) {
System.out.println(dog);
}
});
//lambda简化
dogs.forEach((Dog dog) -> {
System.out.println(dog);
});
//双冒号简化,accept
dogs.forEach(System.out::println);
//使用out.println()作为accept抽象方法的实现
//accept的形参和返回值与println()一样
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
// 创建一个数组
ArrayList<Integer> numbers = new ArrayList<>();
// 往数组中添加元素
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
System.out.println("ArrayList: " + numbers);
// 所有元素乘以 10
System.out.print("更新 ArrayList: ");
// 将 lambda 表达式传递给 forEach
numbers.forEach((e) -> {
e = e * 10;
System.out.print(e + " ");
});
}
//原本应该写为:
.forEach(element -> {
System.out.println(element)
})
//但是System.out.println的参数和传递的参数element 的类型完全匹配,所以这样的时候就可以简化为:
.forEach(System.out::println)
//1. Lambda表达式:
person -> person.getName();
//可以替换成:
Person::getName
//2. Lambda表达式:
() -> new HashMap<>();
//可以替换成:
HashMap::new
包装类
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:
- 赋值操作(装箱或拆箱)
- 进行加减乘除混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList、HashMap等集合类添加基础类型数据时(装箱)
迭代器
Iterator类,可以遍历Collection类型(List和set),不依赖索引遍历。
常用方法:
Iterator<E> iterator:返回迭代器对象,默认指向当前集合的0索引
boolean hasNext():判断当前位置是否有元素,有返回true,没有则false
next():获取当前元素,并将迭代器移向下一元素。
遍历时不能使用集合的增删方法,只能使用迭代器的方法(迭代器只有删除的方法)
增强for循环的底层就是迭代器(增强for只能遍历单列集合和数组)
泛型
格式:<数据类型>(默认:Object),只支持引用类型,不能写基本数据类型。指定数据类型后只能传入该类型或其子类的数据。
好处:统一数据类型,将运行问题提前到了编译阶段,避免了类型转换异常
java的泛型是伪泛型,只在编译阶段有效(兼容之前版本)
泛型种类:泛型类,泛型方法,泛型接口
泛型类:修饰符 class 类名<数据类型>
泛型方法:修饰符 <类型> 返回值类型 方法名(类型 变量名)
泛型接口:1.实现类直接给出数据类型 2.实现类延续泛型,创建对象时再确定数据类型
常见泛型字母代表数据类型:T,E,K,V(type,element,key,value)
泛型不具备继承性,但数据有继承性
泛型通配符
?:代表不确定的类型,?extends E:表示可以传递E及其子类的数据,
?super E:表示可以转递E及其父类的数据。
作用与好处
泛型的作用是为了统一集合中存放的数据类型。
好处:
1、类型安全
泛型的主要目的是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在非常高的层次上验证类型假设。
通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误就可以在编译时被捕获了。将类型检查从运行时挪到编译时有助于Java开发人员更早、更容易地找到错误,并可提高程序的可靠性。
2、消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3、更高的效率
在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
4、潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。