目录
卸载JDK
- 找到环境变量—>系统变量—>JAVA_HOME,将它指示的文件夹删除
- 将它从系统变量中删除
- 将系统变量中的Path中与Java有关的都删了
- 测试:打开终端,输入以下命令,无法识别该命令就卸载成功了
java -version
Scanner
- 创建Scanner实例对象
Scanner scanner = new Scanner(System.in);
-
Scanner.hasNext():判断用户是否输入了数据
Scanner.hasNext+基本数据类型():判断用户输入的下一串空格之间的数据是否为对应类型,如果不是就返回false
eg. Scanner.hasNextInt()。。。 -
Scanner.next():返回空格之间的第一串字符串,不带有空格
Scanner.next+基本数据类型():返回用户输入的下一串空格之间对应类型的数据,不带有空格。如果该数据不是指定类型,就会显示异常
eg. Scanner.nextInt()。。。 -
Scanner.nextLine()返回回车之前的整个字符串、
牛客输入技巧:
- 输入模块用while (in.hasNext())或者if (in.hasNext())包起来都可以,什么都不包也可以,除非有用例不输入任何数据
- 输入模块中,要么全部用Scanner.next()(包括Scanner.nextInt()等),要么全部用Scanner.nextLine()(但是得用String.split(" ")分割)
面向对象的内存分配机制
new一个对象时才分配内存,其中,属性若还没赋值,则数值类默认为0,字符串默认为null
示例代码及内存分配图:
-
由于字符串不是基本数据类型,所以会给他单独分配一个内存空间;
-
“Person p2=p1”相当于将p1的内存地址赋给p2,使得两个变量都指向同一个内存空间,这样改变其中一个的属性值,另一个的也会随之改变
疑问
是不是所有非基本数据类型的对象内存空间都可以用等号来被多个变量名指向?
基本数据类型
强制类型转换注意:
常用引用数据类型
包装类
包装类是对基本数据类型的包装。
装箱:基本数据类转成包装类。例如:
//装箱方法一:构造方法
Integer I = new Integer(1);
Integer I = new Integer("123");
//装箱方法二:手动装箱
Integer I = valueOf(1);
//装箱方法三:自动装箱。其底层就是调用上面的方法,与valueOf(1)等效
Integer I = 1;
拆箱:包装类转成基本数据类。例如:
//拆箱方法一:手动拆箱
int i = I.intValue();
//拆箱方法二:自动拆箱。其底层就是调用上面的方法,与intValue()等效
int i = I;
Integer
注意, 比较两个Integer对象的大小关系时尽量用.equals(Integer)和.intValue()来比较, 原因如下:
public static void main(String[] args) {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //False.因为这是两个不同的引用类对象
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//False.因为i1 直接使用的是缓存中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象。
//下面主要看范围, 如果是-128 ~ 127 就是直接返回
/*1. 如果 i 在 IntegerCache.low(-128)~IntegerCache.high(127),就从数组返回
2. 如果不在 -128~127,就new Integer(i)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i); }*/
Integer m = 1; //底层 Integer.valueOf(1);
Integer n = 1;//底层 Integer.valueOf(1);
System.out.println(m == n); //T
Integer x = 128;//底层 Integer.valueOf(1);
Integer y = 128;//底层 Integer.valueOf(1);
System.out.println(x == y);//False
}
然而Integer和int比较时比较的就是两个的值, 与两个int比较的结果一样.
Integer和Character的常用方法:
Float, Double
两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
String
- String 对象用于保存字符串,也就是一组字符序列
- 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
- String 类实现了接口 Serializable(String 可以串行化:可以在网络传输)和接口 Comparable(String 对象可以比较大小)
- String 是 final 类,不能被其他的类继承
- String 有属性 private final char value[]; 用于存放字符串内容. 要注意:value 是一个 final 类型,不能指向新的地址,但是单个字符内容是可以变化
常用方法
split(String regex)//返回以regex为间隔的子字符串数组,每个成员都不包含regex,但该数组中有可能包含空字符串成员。
String创建时的内存分配机制以及String对象间的比较
String c = "hsp";
System.out.println(a==c);//True
System.out.println(b=="hsp");//False
因此一般用.equals(String)方法判断两个String对象是否相等!
- 大于小于关系比较:
String实现了Comparable接口中的int compareTo(String anotherString)方法. 先依次比较字符的Unicode码, 如果比完最短的字符串后都相等, 就比较二者长度, 小则小, 大则大.例如"A" < “B” = true, “String” < “StringLonger” = true
字符串拼接的两种方法
方法一:
方法二:
注意: 方法一的c放在了常量池中, 方法二的c放在了堆中.
String.toCharArray()方法,以及char和String与数字的加减数字运算区别
示例:
public class string {
//将由数字组成的字符串的第j位数字加1并返回;9加1是0
static String plusOne(String s, int j) {
char[] ch = s.toCharArray();//char[] toCharArray()将字符串转换成字符数组
if (ch[j] == '9')
ch[j] = '0';
else
ch[j] += 1;//char对象自加一相当于对该字符的ASCII码加一,然后返回计算后对应的字符
return new String(ch);//String(char value[])构造方法将字符数组转换成字符串
}
public static void main(String[] args) {
System.out.println(plusOne("999", 1));
char c = 'a';
System.out.println(c+1);//相当于int i = c + 1;System.out.println(i);
String s = "a";
System.out.println(s+1);//String+数字是拼接字符串
}
}
结果:
909
98
a1
判断两个String对象是否相同时,不能用“a == b”,只能用"a.equals(b)"
subString方法使用注意
public String substring(int beginIndex, int endIndex)方法返回的是:序号为beginIndex到endIndex-1的、长度为endIndex-beginIndex的字符数组对应的字符串,而不是序号为beginIndex到endIndex的endIndex-beginIndex+1字符数组的字符串。
StringBuffer
- String和StringBuffer的相互转化
//String——>StringBuffer
String str = "hello tom";
//方式 1 使用构造器. 注意: 返回的是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来
String s1 = new String(stringBuffer3);
常用方法
StringBuffer s = new StringBuffer("hello,张三丰");
//增
s.append("赵敏").append(100).append(true).append(10.5);//"hello赵敏100true10.5"
//删
/* 删除索引为>=start && <end 处的字符 解读: 删除 11~14 的字符 [11, 14) */
s.delete(11, 14); //"hello,张三丰赵敏true10.5"
//改
s.replace(9, 11, "周芷若"); //"hello,张三丰周芷若 true10.5"
//查
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");//6
//插
//在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");//"hello,张三丰赵敏周芷若 true10.5"
//长度
System.out.println(s.length());//22
//反转
StringBuffer revS = s.reverse();//"丰三张,olleh"
String的不可变性
保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
String、StringBuffer、StringBuilder 的区别
- 线程安全性:
String 中的对象是不可变的,也就可以理解为常量,所以线程安全。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
- 性能:
String 是不可变的,每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。
StringBuilder 在相同情况下相比使用 StringBuffer 获得 10%~15% 左右的性能提升
三元运算符
i = n == Integer.MAX_VALUE ? -1 : n;
等效于
if (n == Integer.MAX_VALUE)
i = -1;
else
i = n;
但是,
Object o = true ? new Integer(1) : new Double(2.0);
结果为o=1.0. 原因是, 三元运算符接受变量o的类型Object要转成后面两个表达式的类型中优先级最高的一个, 即double要高于int, 故o转成Double型.
然而, 这条语句等效为if结构后o=1, 因为if和else的执行语句是相互独立的.
数组
数组填充
//给数组n[]填充1
Arrays.fill(n, 1)
for循环结构
三种迭代方式:
- 普通for循环
- 增强for循环
- 迭代器
idea技巧之快速键入for循环
普通for循环:输入fori,回车
增强for循环:输入大写的I,回车
Collection集合
Java集合框架的所有接口和类都组织在java.util包中,支持两种类型的容器:Collection集合和map映射(键值对)
Collection接口底下的实现、继承关系如图:
不同集合的主要特点:
- Set用于存储一组不重复的元素
- List用于存储一个有序元素的集合
- Stack用于存储采用后进先出方式处理的对象
- Queue用于存储采用先进先出方式处理的对象
- PriorityQueue用于存储按照优先级顺序处理的对象
除开PriorityQueue没实现Cloneable接口外,其他所有集合的具体类都实现了该接口以及Serializable接口,并且除开PriorityQueue外,其他都是可克隆和可序列化的
List线性表
List接口是Collection接口的子接口
- List集合类中元素顺序与添加的顺序一致
- 支持索引
- LIst接口的实现类有很多,常用的有ArrarList、LinkedList和Vector
import java.util.ArrayList;
import java.util.List;
public class List_ {
public static void main(String[] args) {
List list = new ArrayList();
//1. List集合类中元素顺序与添加的顺序一致
list.add("a");
list.add("b");
list.add("c");
System.out.println(list);
//2. 支持索引
System.out.println(list.get(2));
}
}
List常用方法:
import java.util.ArrayList;
import java.util.List;
public class ListMethod {
public static void main(String[] args) {
List list = new ArrayList();
//1.void add(int index, E element),void add(int index, E element):在最后一个或index位置插入element元素
list.add("a");
list.add("c");
list.add(1,"b");//插入到指定位置
System.out.println(list);
//2.boolean addAll(int index, Collection<? extends E> c):在index插入c的所有元素
List list2 = new ArrayList();
list2.add("a1");
list2.add("a2");
list.addAll(1,list2);
System.out.println(list);
//3.E get(int index):获取指定index的元素
System.out.println(list.get(3));
//4.int indexOf(Object o):返回元素o首次出现的索引
System.out.println(list.indexOf("c"));
//5.int lastIndexOf(Object o):返回元素o末次出现的索引
System.out.println(list.lastIndexOf("c"));
//6.E remove(int index):移除指定index的元素
list.remove(3);
System.out.println(list);
//7.E set(int index, E element):将index位置的元素替换为element.注意:index必须小于等于List的索引上界
list.set(0,"a0");
System.out.println(list);
//8.List<E> subList(int fromIndex, int toIndex):返回从fromIndex对应的元素到toIndex前一个元素的子集合
List sublist = list.subList(0, 3);
System.out.println(sublist);
//public boolean contains(Object o):查询是否包含o元素
if (list.contains(a))
System.out.println("list包含元素a");
//LinkedList特有方法
LinkedList linkedList = new LinkedList();
//1.private void addFirst(E e),void addLast(E e):Inserts the specified element at the beginning of this list
//2.void linkBefore(E e, Node<E> succ):Inserts element e before non-null Node succ
//3.删除LinkedList的首,末位元素
linkedList.removeFirst();
linkedList.removeLast();
}
}
:用增强for循环遍历List:
for (Object o :list) {
System.out.println(o);
}
ArrayList数组线性表
注意事项:
- 元素可以为null
- 底层是用数组实现数据存储的
- 基本等同于Vector,但ArrayList是线程不安全的,在多线程的情况下不建议使用ArrayList
底层操作机制源码分析:
如果使用的是无参构造器,则底层数组的初始容量为0,第一次添加元素时扩容为10;如果使用的是指定List大小的构造器,则底层数组初始容量为指定的大小;后续添加元素超过数组容量后都扩容为1.5倍.
数组转换成集合:
使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 add/remove/clear 方法会抛出异常,并且传递的数组必须是对象数组,而不能是基本类型。因为Arrays.asList() 方法返回的并不是 java.util.ArrayList ,而是 java.util.Arrays 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。正确的方法有:1. 手动装换;2. List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
;3. 使用 Java8 的 Arrays.stream方法
idea技巧之Debug
点击debug开始语句的左边产生断点,右键Debug
如果只进入该程序的下一语句,则点击Step into
如果要进入语句调用的方法定义之处,则取消勾选Setting–>Debugger–>Stepping–>Do not step into Groovy classes,并点击红色的Force step into ;假如没有方法,就进入下一步
如果Force step into 后要返回上一级语句,则点击Step out
如果要查看对象的完整属性值,先更改Debuger的设置,去勾选
然后就可以将鼠标放在对象名上,并点击"+"
就可以查看
Vector向量
特点:
- 线程同步,线程安全
- 效率不高
源码分析:
无参构造初始容量为10;每次两倍扩容
Stack栈
- Stack继承了Vector
- 栈这种数据结构可以看作是一个特殊的线性表,访问、插入和删除元素只能在栈尾(栈顶)进行
特有方法:
构造方法Stack(): 创建一个空栈;
boolean empty(): 如果栈是空的,则返回true;
E peek(): 返回栈中的顶部元素;
E pop(): 返回并删除栈顶元素;
E push(E o): 增加一个新元素o到栈顶;
int search(Object o): 返回该栈中指定元素的位置
LinkedList链表
特点:
- 底层维护了一个双向链表
- 元素的添加和删除不是通过数组完成的,效率相对比较高
- 线程不安全
底层操作机制:
每个LinkedList维护了first和last两个属性,分别指向首节点和尾节点,每个节点(Node对象)维护了prev、next、item三个属性,分别指向前一个节点、下一个节点和当前节点的对象
与ArrayList对比:
更适合增删,较不适合改查
Set规则集
- 不能存放重复的元素, 只可以添加一个 null
- 对象存放数据是无序的(即添加的顺序和取出的顺序不一致),但取出的顺序也是固定的
- 不能使用索引,因此只能用迭代器iterator和增强for循环来取出元素
常用方法:
//添加元素。添加成功返回true,添加失败(已有重复元素)返回false
boolean add(E e);
此外,由于Set继承Collection,因此也实现了boolean remove(Object o), boolean addAll(Collection<? extends E> c), boolean contains(Object o), boolean retainAll(Collection<?> c)(保留该Set对象和c的公共元素)等方法。
HashSet散列集
底层操作机制:
其数据在底层使用HashMap存放的,而HashMap底层是数组+链表+红黑树。
示例: 添加元素e的过程
- 调用元素e的Object.hashCode()方法计算其hash值(间接地, 并不是直接返回这个方法返回的值), 再将这个值转化为一个索引值i
- HashSet底层的HashMap的数据存放是一个存放着Node类型的数组table, Node可以是是一个链表或红黑树; 先找到这个数组索引为i的位置是否已经存放有元素, 如果没有元素, 直接存放e
- 如果有, 就来判断这个位置的链表或红黑树中的每一个对象和e是否相同(存储地址相同, 为同一个对象)或相等(这两个对象的hash值相等并且调用.equals方法比较两个方法时返回true。因此,重写 equals() 时必须重写 hashCode() 方法), 如果相等或相同, 则返回false
- 如果没有相同或相等的, 则将e添加到最后并返回true; 如果该Node节点为一个链表且长度大于等于8, 则先判断数组table的大小是否大于等于64, 是就将该Node节点变成一棵红黑树并将e添加到红黑树中并将e添加到红黑树中, 小于64就先将数组扩容并将e添加到链表末尾,具体扩容机制如下
HashMap底层数组table的扩容机制:
注意:只要这个HashMap中存放的总元素个数大于等于当前临界值threshold就扩容,而不是看数组table中的非空元素个数是否大于等于threshold
重写 equals() 时必须重写 hashCode() 方法
idea中用快捷键Alt+Insert快捷键, 点击"equals() and hashCode()"自动重写
举例:如果我们需要设定,当两个类型为a的对象的age和s属性都相等时就视为重复对象而无法加入同一个HashSet对象,那么就需要重写.equals(Object)和.hashCode()方法,如下
class a{
private int age;
private String s;
//当age和s的值相同时, .equals()返回true,否则false
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
a a = (a) o;
return age == a.age && Objects.equals(s, a.s);
}
@Override
public int hashCode() {
return Objects.hash(age, s);
}
}
LinkedHashSet链式散列集
继承了HashSet, 而且其中的元素可以按照插入的顺序来获取
底层机制:
底层与HashSet的相似, 只是还维护了一条双向链表, 以维护元素的顺序.
所以不需要维护元素插入的顺序, 就应该用HashSet, 它会比LinkedHashSet更加高效
TreeSet树形集
- 可以确保规则集中的元素是有序的, 其中元素可以用两种方式进行比较: Comparable接口和Comparator接口.
- TreeSet判定元素是否重复是用Comparable接口或Comparator接口,换句话说TreeSet中不允许出现排序结果是"等于"的两个元素,尽管它们俩的hash值相等并且.equals方法判断为true。详见D:\IJ IDEA\Projects\真题\src\main\java\hashCode.java
除包含在Set中的其他常用方法,还实现了:
-
SortedSet接口中的方法:
E first(): 返回第一个元素
E last(): 返回最后一个元素
SortedSet subSet(E fromElement, E toElement): 返回从fromElement到toElement的前一个元素的所有元素(左闭右开)
SortedSet headSet(E toElement): 返回toElement之前的元素, 不包括toElement
SortedSet tailSet(E fromElement): 返回fromElement之后的元素, 包括fromElement -
NavigableSet接口中的方法:
E lower(E e): 返回小于e的最大元素
E floor(E e): 返回小于等于e的最大元素
E ceiling(E e): 返回大于等于e的最小元素
E higher(E e): 返回大于e的最小元素
(注意:e可以不包含在该TreeSet对象中,只要可以与里面的元素比较就行)
E pollFirst(): 删除第一个元素, 并返回被删除的元素
E pollLast(): 删除最后一个元素, 并返回被删除的元素
如果使用无参构造方法创建一个TreeSet, 则会假定元素的类实现了Comparable接口并使用compareTo方法来比较元素.如果没有实现Comparable接口,那么就必须调用TreeSet(Comparator comparator)构造方法,否则会报错.但只要调用了TreeSet(Comparator comparator)就一定用comparator的compare方法对元素进行排序.
规则集Set与线性表List的性能比较:
Set的元素查找操作比List高效很多
List可以通过索引来访问元素, Set则不行
PriorityQueue优先队列
Map映射
- Map是一个存放“键/值对”key-value集合的容器对象,一对key-value是一个条目(接口Map.Entry)
- key不能重复,原因和HashSet一样,见上面的底层源码机制
- key和value都可以为null,但是最多只能有一个为null的key,而为null的value可以为多个
常用方法:
V put(K key, V value):将一个条目放入映射表中,如果key已存在则用value替代原来的值,最后返回放入条目的值value;
V remove(Object key):将指定键对应的条目从映射表中删除,并返回被删除条目的值;
void clear():删除映射表中的所有条目;
boolean containsKey(Object key):判断映射表中是否有键为key,若有则返回true;
boolean containsValue(Object value):判断映射表中是否有值为value,若有则返回true;
boolean isEmpty():判断该映射表是否为空,若空则返回true;
V get(Object key):返回指定键key对应的值;
int size():返回映射表中的条目数;
Set keySet():返回一个包含映射表中所有键的规则集
Collection values():返回一个包含映射表中所有值的集合
Set<Map.Entry<K, V>> entrySet():返回一个包含映射表中所有条目(Map.Entry)的规则集,Entry的常用方法有:K getKey()——返回该条目的键;K getValue()——返回该条目的值;V setValue(V value)——将该条目中的值替换为新值
V getOrDefault(Object key, V defaultValue):返回键key对应的值,如果没有key这个键就增加一个条目,键为key、值为defaultValue,并返回新增加条目的值defaultValue
void forEach(BiConsumer<? super K, ? super V> action)——为映射表中的每一个条目执行一个操作
遍历方法:
- 可以用keySet()、values()和entrySet()方法分别将所有的键、值和条目取到集合中,再用for循环和迭代器Iterator分别遍历
- 用lambda表达式执行Map.forEach(action)方法:
Map对象.forEach((key, value) -> 对每个key和value执行的语句) );
HashMap散列映射
底层机制与HashSet底层基本原理相同。其中,每一个Node实现了Map.Entry<K,V>接口;table数组的索引值是由键的值通过hashCode间接算出来的;判断重复的对象时键
LinkedHashMap链式散列映射
使用无参构造LinkedHashMap()来创建LinkedHashMap,元素可以按照插入的顺序来排序(插入顺序);使用LinkedHashMap(initialCapacity, loadFactor, true)来创建,元素可以按最后一次访问的从早到晚顺序来排序(访问顺序),其中initialCapacity为table数组的初始容量、int类型、可以设为16,loadFactor为加载因子、float类型、一般为0.75f。
TreeMap树形映射
- 键可以使用Comparable接口或Comparator接口来排序。
- 判定两个元素的键是否重复的方法同TreeSet
除了Set的方法外,它还实现了:
-
SortedMap<K, V>接口中的方法:
K firstKey():返回映射中的第一个键
K lastKey():返回映射中的最后一个键
SortedMap<K,V> headMap(K toKey):返回键小于toKey的那部分映射
SortedMap<K,V> tailMap(K fromKey):返回键大于等于toKey的那部分映射 -
NavigableMap<K, V>接口中的方法:
K lowerKey(K key):返回小于key的最大键
K floorKey(K key):返回小于等于key的最大键
K ceilingKey(K key):返回大于等于key的最小键
K higherKey(K key):返回大于key的最小键
(注意:如果没有满足要求的键,就返回null)
Map.Entry<K,V> pollFirstEntry():删除并返回映射中的第一个条目Map.Entry<K,V> pollLastEntry():删除并返回映射中的最后一个条目
Hashtable
实现了Map接口,它除了 Hashtable中的更新方法是同步(synchronized)的、或者说是线程安全的,并且键和值都不能为null以外,它与HashMap的用法是一样的。
ConcurrentHashMap
线程安全问题:
jdk1.7:
jdk1.8:
ConcurrentHashMap在JDK 1.7 的最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大。而HashTable是对整个数组加锁,效率最低。
ConcurrentHashMap的key或value都不能为null。value如果为null,那么多线程情况下调用.get(key)方法得到null时,无法判断是key的value为null还是没有key而返回null,产生了二义性。
Properties
总结:如果更新映射时不需要保持元素顺序,就使用HashMap,此时它最高效。
Collections
注意:不是Collection接口
用于创建包含单一项的不可变规则集、线性表或映射的静态方法:singleton…系列方法。
用于返回集合或映射的只读视图的静态方法:unmodifiable…系列方法。
对用上述方法创建的对象进行修改将抛出异常。
Comparator接口的使用
Comparator接口用来比较比较不同类的元素
例如java.util.Arrays中有排序方法public static void sort(T[] a, Comparator<? super T> c),这时有一个字符串数组S,按照每个字符串的长度升降序排列
//升序
Arrays.sort(S, (a, b) -> a.length - b.length);
//降序
Arrays.sort(S, (a, b) -> b.length - a.length);
其中升序中的(a, b) -> a.length - b.length是一个lambda表达式,它实现了Comparator接口中的一个方法int compare(T a, T b),实现内容是return a.length - b.length。
疑问
这个int compare(T a, T b)方法的原理是什么?他为什么可以决定序列的顺序?
jconsole工具
一种监控线程运行的工具。通过终端运行:
反射
反射机制可以让我们通过外部文件配置,在不修改源码的情况下来控制程序。符合设计模式的ocp原则(开闭原则:不修改源码的情况下扩容功能)
- 原理
-
反射可以完成
-
反射的主要类
-
反射的优缺点
Class类
public static void main(String[] args) throws ClassNotFoundException {
//1. Class 也是类,因此也继承 Object 类
//2. Class 类对象不是 new 出来的,而是系统创建的
//(1) 传统 new 对象
Cat cat = new Cat();
/* 底层通过ClassLoader类的loadClass方法来创建对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//(2) 反射方式
// 如果没有注销前面的 Cat cat = new Cat(),那么接下来底层就不会经过ClassLoader 类的 loadClass,因为同一个类只会加载一次
Class cls1 = Class.forName("Cat");
/* 底层仍然通过ClassLoader类的loadClass方法来创建对象*/
//3. 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
Class cls3 = Class.forName("Dog");
System.out.println(cls3.hashCode());
}
- Class常用方法
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String classAllPath = "com.hspedu.Car";
//1 . 获取到 Car 类 对应的 Class 对象
Class<?> cls = Class.forName(classAllPath);
//2. 输出 cls
System.out.println(cls); //class com.hspedu.Car
System.out.println(cls.getClass());//class java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//(包名)com.hspedu
//4. 得到全类名
System.out.println(cls.getName());//com.hspedu.Car
//5. 通过 cls 创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car);//相当于打印car.toString()
//6. 通过反射获取非private属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8. 得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName());//名称
}
}
- 获取 Class 类对象的不同方式
注意:如果同一个程序中5、6获取的类对应,比如int和Integer,那么得到的Class对象是同一个。
- 哪些类有Class
类加载
注意:
new是静态加载,因此即使程序没有运行到通过new创建的这个对象,该对象有错还是会报错;而通过反射机制创建的对象只有运行到创建的语句才会报错。
- 类加载的时机
- 类加载过程
注意:加载和连接阶段完全是由JVM控制执行的,初始化由程序员来控制,JVM执行
加载阶段:
将字节码从不同的数据源(class文件、jar包或网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象。
连接-准备阶段:
class A {
//1. n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存
//2. n2 是静态变量,分配内存 n2 是默认初始化 0 ,而不是 20
//3. n3 是 static final 是常量, 他和静态变量不一样, 因为一旦赋值就不变 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
连接-解析阶段:
虚拟机将常量池中的符号引用替换为符号引用。
初始化:
注意:
1.按照先后顺序执行源代码;如果不实例化类的对象而直接使用类的静态属性,也会导致类的加载
public class A {
static {
a = 2;
}
static int a = 1;
}
public class B {
static int b = 1;
static {
b = 2;
}
}
public class test{
public static void main(String[] args){
System.out.println(A.a);
//1
System.out.println(B.b);
//2
}
}
2.类加载时有同步机制控制,因此保证了某个类在内存中最多只有一个Class对象
通过反射获取类的结构信息
- java.lang.Class
注意:getConstructors方法无法获取父类的构造方法
- java.lang.reflect.Field
- java.lang.reflect.Method
- java.lang.reflect.Constructor
通过反射创建对象
import java.lang.reflect.*;
public class CreateInstance {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1. 先获取到 User 类的 Class 对象
Class<?> userClass = Class.forName("User");
//Class<User> userClass = User.class;
//2. 通过 public 的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过 public 的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public 的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object ycr = constructor.newInstance("ycr");
System.out.println("ycr=" + ycr);
//4. 通过非 public 的有参构造器创建实例
//4.1 得到 private 的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问 private 构造器/方法/属性, 反射面前,都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);
//5.获取所有构造器
Constructor<?>[] constructors1 = userClass.getConstructors();
Constructor<?>[] constructors2 = userClass.getDeclaredConstructors();
}
}
class User{
private int age = 10;
private String name = "韩顺平教育";
public User() {//public 无参构造器
}
public User(String name) {//public 的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
通过反射访问属性
import java.lang.reflect.*;
public class AccessField {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1. 得到 Student 类对应的 Class 对象
Class<?> stuClass = Class.forName("Student");
//2. 创建对象
Object o = stuClass.newInstance();
//3. 使用反射得到 age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来赋值属性
System.out.println(o);//
System.out.println(age.get(o));//通过反射来获取属性
//4. 使用反射操作 name 属性
Field name = stuClass.getDeclaredField("name");
//对 name 进行暴破, 可以操作 private 属性
name.setAccessible(true);
name.set(null, "ycr~");//通过反射来赋值有权限的属性;因为 name 是 static 属性,因此对象除了写成o,也可以写成null
System.out.println(o);
System.out.println(name.get(o)); //通过反射来获取有权限的属性
System.out.println(name.get(null));
//5.获取所有属性
Field[] fields1 = stuClass.getFields();
Field[] fields2 = stuClass.getDeclaredFields();
}
}
class Student {//类
public int age;
private static String name;
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
通过反射调用方法
import java.lang.reflect.*;
public class AccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
//1. 得到 Boss 类对应的 Class 对象
Class<?> bossCls = Class.forName("Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用 public 的 hi 方法
//Method hi = bossCls.getMethod("hi", String.class);//OK
//3.1 得到 hi 方法对象
Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
//3.2 通过反射调用hi方法
hi.invoke(o, "ycr~");
//4. 调用 private static 方法
//4.1 得到 say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为 say 方法是 private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
//System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为 say 方法是 static 的,还可以这样调用 ,可以传入 null
System.out.println(say.invoke(null, 200, "李四", '女'));
//5. 在反射中,如果方法有返回值,返回值的类型统一为Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String
//6. 调用无参方法
Object monster = bossCls.getMethod("m1").invoke(o);
System.out.println(monster);
//7.获取所有方法
Method[] methods1= bossCls.getMethods();
Method[] methods2= bossCls.getDeclaredMethods();
}
}
class Boss {//类
public int age;
private static String name;
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通 public 方法
System.out.println("hi " + s);
}
}
class Monster {
@Override
public String toString() {
return "Monster{}";
}
}