面向对象的三个特征:封装、继承、多态。
基础知识点
类型转换:
Boolean类型不能向其他类型转换,剩下的所有数据类型均可转换为double;
三元运算符:
例: int a = b > c ? b : c;//b >c 若为真,则a = b,为假,a = c;
数据输入Scanner:
1:导包: import java.util.Scanner;
2:创建对象: Scanner sc = new Scanner(System.in);
3:接收数据:int i = sc.nextInt(); //录入值为int类型;
String s = sc.nextLine(); //录入值为String类型;
循环语句
1:switch
格式:
switch(表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
.........
default:
语句体n+1;
break;
}
首先计算表达式的值,将其和底下的case后的值(1,2,3)进行比较,若有相同项,就执行该case下的语句体.若都不匹配,就执行defaul后的语句体.
2:for循环
格式:
for(初始化语句;条件判断语句;条件控制语句){
循环体语句;
}
例如:
for(int i = 0; i < 10; i++) {
System.out.println(i);
}
for循环最常用在数组,集合等的遍历中,极为常见,不在赘述.
3:while循环
格式:
while(条件判断语句){
循环体语句;
条件控制语句;
}
例如:
int i = 0;
while(i < 10){
System.out.println(i);
i++;
}
4:do…while循环
格式:
初始化语句;
do{
循环体语句;
条件控制语句;
}while(条件判断语句);
死循环:
for(; ; ){}
while(true){}
随机数Random
格式:
导包: import.java.util.Random;
创建对象: Random r = new Random();
产生随机数: int num = r.nextInt(10); //产生0~9的随机数.
数组
1:静态初始化:
格式: int [] arr = {1,2,3,4,5,6};
2:动态初始化:
格式: int [] arr = new int[3];
方法
格式:
public static void 方法名(){
方法体;
}
无参方法:
public static void 方法名(){
方法体;
}
一般情况下无参方法中就含有输出语句.
带参方法:
public static void method(int i) {
方法体;
}
参数可以包含一个或多个,且类型不限,也可使用自己定义的类型;
调用时需要给定参数值.例: method(20);
形参和实参:
形参: 方法中的参数.
实参: 方法调用中的参数.
带返回值的方法:
格式:
public static 数据类型 方法名(参数) {
return 数据;
}
例:
public static boolean isEvenNumber( int number ) {
return true ;
}
例:
public static void main(String[] args) {
int result = getMax(10,20);
System.out.println(result);
}
public static int getMax(int a, int b) {
if(a > b) {
return a;
} else {
return b;
}
}
}
方法的重载
必须满足:
1:多个方法在同一个类中;
2:多个方法具有相同的方法名;
3:多个方法的参数不同,类型不同或者数量不同;
ps:方法重载就是,在调用方法时,因为多个方法的方法名相同,所以JVM会根据调用方法时所提供的参数类型,参数个数;来选择调用对应的方法.
方法的参数传递
public class ArgsDemo01 {
public static void main(String[] args) {
int number = 100;
System.out.println(number);
change(number);
System.out.println(number);
}
public static void change(int number) {
number = 200;
}
}
输出: 100// 100
说明形参的改变,并不影响实参的值.
*但是*:
若方法的形参是引用类型的,则形参的改变会影响实参.
类和对象
1: 类即为自己定义的一个类,其中包括成员变量和成员方法;
2: 若是用特殊修饰符修饰的成员变量,比如私有private,需要创建get,set 方法,set在测试类 中设置该变量,get在测试类中获取/输出该对象.
3: 在使用时需要创建对象,且可以通过对象名来调用类中的成员变量和成员方法.
构造方法
1: 构造方法存在于类中,包括无参构造和带参构造,可根据需要调整带参构造中参数的个数.
2: 构造方法主要用于完成对象数据的初始化,若一个类中没有给出构造方法,那么创建对象的过程中在类中便会有一个JVM定义好的无参构造.
3: 如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法.
String类
字符串的比较
1:==号的比较:
比较基本数据类型:比较的是具体的值 // int i = 8;
比较引用数据类型:比较的是对象地址值 // int arr[] = {1, 2, 3};
equals的比较:
1, equals()方法对于字符串来说是比较内容的,而对于非字符串来说是比较,其指向的对象是否相同的。
2, 如果没有覆盖重写equals方法,那么Object类中默认进行==运算符的对象地址比较,只要不是同一个对象,结果必然为false。
- 如果希望进行对象的内容比较,即所有或指定的部分成员变量相同就判定两个对象相同,则可以覆盖重写equals方法。
import java.util.Objects;
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object o) {
// 如果对象地址一样,则认为相同,这里的this与o都是变量,而不再是对象,
//所以对其进行"=="的比较,比较的就是数值是否相等,或字符串内容是否相等!!
if (this == o)
return true;
// 如果参数为空,或者类型信息不一样,则认为不同
if (o == null || getClass() != o.getClass())
return false;
// 转换为当前类型
Person person = (Person) o;
// 将o转换为基本类型相等的变量,并且将引用类型交给
\\java.util.Objects类的equals静态方法取用结果
if (this.name.equals(p.getName()) && this.age == p.getAge()) {
return true;
} else {
return false;
}
StringBuilder类
这是一个可变的字符串,
1: 添加字符串的方法:
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append("World");
sb.append("java");
sb.append("hello").append("world").append("java").append(100);
2: 返回相反的字符序列:
sb.reverse();
3: String 转换为StringBuilder只需通过构造方法即可:
StringBuilder sb = new StringBuilder();
sb.append("hello");
4: StringBuilder转换为String需要通过toString方法:
String s = sb.toString();
集合
( 一 ): Collection集合:
该接口是所有集合接口的父类,不可直接实例化,需要通过多态的方式实例化
Collection c = new ArrayList();
<1>: List集合:
1, 该集合接口继承自Collection集合,是其子类.
2, 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素
3, 与Set集合不同,列表通常允许重复的元素
4, 必须通过多态的实例化方法,才能实例化
特点
- 有索引
- 可以存储重复元素
- 元素存取有序
1: ArrayList集合:
1, ArrayList是最常用的集合,其内部实现是一个数组
2, ArrayList的大小是可以动态扩充的。对于元素的随机访问效率高
成员方法:
public boolean remove(Object o)
删除指定的元素,返回删除是否成功
public E remove(int index)
删除指定索引处的元素,返回被删除的元素
public E set(int index,E element)
修改指定索引处的元素,返回被修改的元素
public E get(int index)
返回指定索引处的元素
public int size()
返回集合中的元素的个数
public boolean add(E e)
将指定的元素追加到此集合的末尾
public void add(int index,E element)
在此集合中的指定位置插入指定的元素
2: LinkedList集合:
特有方法:
- public void addFirst(E e)
在该列表开头插入指定的元素 - public void addLast(E e)
将指定的元素追加到此列表的末尾 - public E getFirst()
返回此列表中的第一个元素 - public E getLast()
返回此列表中的最后一个元素 - public E removeFirst()
从此列表中删除并返回第一个元素 - public E removeLast()
从此列表中删除并返回最后一个元
<2>: Set集合:
特点:
- 元素存取无序
- 没有索引、只能通过迭代器或增强for循环遍历
- 不能存储重复元素
- 必须通过多态的实例化方法,才能实例化
1:HashSet集合:
特点:
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
2: LinkedHashSet集合:
特点:
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
3: TreeSet集合:
1, 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法
- TreeSet():根据其元素的自然排序进行排序
- TreeSet(Comparator comparator) :根据指定的比较器进行排序
2, 没有带索引的方法,所以不能使用普通for循环遍历
3, 由于是Set集合,所以不包含重复元素的集合
方法一:
自然排序Comparable的使用:
在定义好的Student类中重写compareTo方法,
@Override
public int compareTo(Student s) {
int num = s.age - this.age;//根据年龄的大小排序
int nu = num == 0 ? this.name.compareTo(s.name) : num;//如果年龄相同时,根据姓名首字母排序
return nu;
}
方法二:
在测试类中直接调用 TreeSet 的带参构造方法,在其内重写了compare方法:
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student> () {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
( 二 ): Map集合
特点:
- 键值对映射关系
- 一个键对应一个值
- 键不能重复,值可以重复
- 元素存取无序
Map集合的遍历:
方式一:
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(22, "赵敏");
map.put(32, "黄蓉");
map.put(38, "小龙女");
// 获取所有键的集合。用keySet()方法实现
Set<Integer> keySet = map.keySet();
//遍历键的集合,获取到每一个键。用增强for实现
for (Integer key : keySet) {
//根据键去找值。用get(Object key)方法实现
String value = map.get(key);
System.out.println(key + "," + value);
}
}
32,黄蓉
22,赵敏
38,小龙女
方法二:
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(22, "赵敏");
map.put(32, "黄蓉");
map.put(38, "小龙女");
// 获取所有键值对对象的集合
Set<Map.Entry<Integer, String>> e = map.entrySet();
// 遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<Integer, String> m : e) {
// 根据键值对对象获取键和值
Integer key = m.getKey();
String value = m.getValue();
System.out.println(key + "," + value);
}
}
32,黄蓉
22,赵敏
38,小龙女
注意:
- Map集合的键值对不一定都是基本数据类型,还可以是自己定义的某个类!
- Map集合的实例化也是要通过多态的形式完成,通过其子类HashMap
- Properties集合:
- Properties可以保存到流中或从流中加载
- 属性列表中的每个键及其对应的值都是一个字符串
//错误
Properties<String,String> prop = new Properties<String,String>(); //错 误
Properties prop = new Properties();
prop.put("itheima001", "林青霞");
prop.put("itheima002", "张曼玉");
prop.put("itheima003", "王祖贤");
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
Object value = prop.get(key);
System.out.println(key + "," + value);
}
}
}
- 特有方法:
public static void main(String[] args) {
Properties prop = new Properties();
prop.setProperty("01", "狗剩");
prop.setProperty("02", "铁柱");
prop.setProperty("03", "楞娃");
prop.put("04", "二狗");
// 使用此属性列表中指定的键搜索属性;
System.out.println(prop.getProperty("02"));// 铁柱
// 从该属性列表中返回一个不可修改的键集
Set<String> names = prop.stringPropertyNames();// [03,02,01]
for (String key : names) {
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}
}
Properties和IO流的结合:
该集合和IO流相结合,使得从文件到集合或从集合到文件变得更加简洁;## 标题
public static void main(String[] args) throws IOException {
show();
run();
}
//从文件到集合
private static void run() throws IOException {
Properties prop = new Properties();
FileReader fr = new FileReader("E:\\JAVA SE\\java.txt");
prop.load(fr);//从输入字节流读取属性列表(键和元素对)
fr.close();
System.out.println(prop);
}
//从集合到文件
private static void show() throws IOException {
Properties prop = new Properties();
prop.setProperty("01", "狗剩");
prop.setProperty("02", "铁柱");
prop.setProperty("03", "楞娃");
FileWriter fw = new FileWriter("E:\\JAVA SE\\java.txt");
prop.store(fw, null);//从集合写入文件
fw.close();
}
( 三 ):Collections集合工具类
-
public static void sort(List list)
将指定的列表按升序排序 -
public static void reverse(List<?> list)
反转指定列表中元素的顺序 -
public static void shuffle(List<?> list)
使用默认的随机源随机排列指定的列表#
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
// 将指定的列表按升序排序
Collections.sort(list);
System.out.println(list);// [10, 20, 30, 40, 50]
// 反转指定列表中元素的顺序
Collections.reverse(list);
System.out.println(list);// [50, 40, 30, 20, 10]
// 使用默认的随机源随机排列指定的列 表
Collections.shuffle(list);
System.out.println(list);// [20, 40, 30, 10, 50]
}
( 四 ): 迭代器:
1, 迭代器的三个方法:
- Iterator()//获取迭代器
- hasNext()//判断集合是否有数组
- next()//从集合中取出元素
迭代器遍历List集合:
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer i = it.next();
System.out.println(i);
}
}
结论:
1.迭代是有一个指针 指向了集合的首位置
2.每调用一次 next 方法 这个指针 就向向下移到动一格
( 五 ): 增强for:
定义格式:
for(元素数据类型 变量名 : 数组/集合对象名) {
循环体;
}
( 六 ): 关于Java集合的7 个问题
1.关于LinkList和ArrayList
-
ArrayList:内部实现是个数组,其中的元素可以通过index获取。但是,如果一个数组满了的话,我们就必须重新分配一个更大的数组然后把所有元素移动到这个新数组,其时间复杂度为O(n)。添加或删除一个元素时也需要移动数组中的其它元素。这就是ArrayList的缺点。
-
LinkedList:是一个双向链表。因此如果我们要获取中间元素的话,我们就需要从头开始遍历;另一方面,添加或删除一个元素就变得很简单,因为只需要对这个链表本身操作即可。
-
LinkList:每个节点还需要额外的两个指针,分别指向前一个节点和下一个节点
-
ArrayList:只需要一个数组
2, 如何将一个List转换成一个int[] 数组?
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
int[] arr = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
arr[i] = list.get(i);
System.out.print(arr[i] + ", ");
}
}
3, 如何将int[] 转换成List?
int[] array = {1,2,3,4,5};
List<Integer> list = new ArrayList<Integer>();
for(int i: array) {
list.add(i);
}
4, 最有效移除集合元素的方式:
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(30);
list.add(20);
list.add(50);
list.add(10);
list.add(40);
//首先获取迭代器
Iterator<Integer> itr = list.iterator();
//判断集合中是否有元素
while (itr.hasNext()) {
//获取元素
Integer next = itr.next();
//移除元素
itr.remove();
}
//遍历集合
for (Integer i : list) {
System.out.println(i);
}
}
输出为空
5, 把List转换成Set的最简单的方式有两种方式:
- 方法一:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张斯捷");
list.add("张国荣");
list.add("张西林");
list.add("张斯捷");
//List被转换成了Set集合
Set<String> set = new HashSet<>(list);
Iterator<String> i = set.iterator();
while (i.hasNext()) {
String next = i.next();
System.out.println(next);
}
}
输出:
张国荣
张斯捷
张西林
- 方法二:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张斯捷");
list.add("张国荣");
list.add("张西林");
list.add("张斯捷");
// List被转换成了Set集合
Set<String> set = new TreeSet<>();
set.addAll(list);
Iterator<String> i = set.iterator();
while (i.hasNext()) {
String next = i.next();
System.out.println(next);
}
}
如果关心顺序,把上面那个HashSet换成LinkedHashSet即可。
6, 如何移除ArrayList中的重复元素:
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张斯捷");
list.add("张国荣");
list.add("张西林");
list.add("张斯捷");
// List被转换成了Set集合
Set<String> set = new TreeSet<>(list);
//清空list集合
list.clear();
//将set中的元素全部加进list;
list.addAll(set);
Iterator<String> i = list.iterator();
while (i.hasNext()) {
String next = i.next();
System.out.println(next);
}
}
7, 复制List集合:
- 方法一:
ArrayList cList = new ArrayList(list); //在末尾的括号加要复制的List集合即可; - 方法二:
public class CollectionsTest {
public static void main(String[] args) {
// 创建一个集合list
ArrayList<Integer> list = new ArrayList<Integer>();
// 给集合中添加元素
list.add(66);
list.add(88);
list.add(126);
list.add(368);
list.add(0);
list.add(-99);
// Arrays.asList是把数组转变为集合的方法
List dest = Arrays.asList(new Integer[list.size()]);
System.out.println("dest集合的大小为: " + dest.size());
// 把list集合中的元素复制到dest集合中
Collections.copy(dest, list);
System.out.println("dest集合为:" + dest);
}
}
( 七 ): 集合的相互转换:
1, List转换为Array(数组):
List<String> list = new ArrayList<String>();
list.add("China");
list.add("Switzerland");
list.add("Italy");
list.add("France");
String [] countries = list.toArray(new String[list.size()]);
2, Array转换为List:
String[] countries = {"China", "Switzerland", "Italy", "France"};
List list = Arrays.asList(countries);
3, Map转换为List:
public static void main(String[] args) {
Map<Integer, String> m = new HashMap<>();
m.put(1, "张思杰");
m.put(2, "张思涵");
m.put(3, "刘老六");
Set<Integer> keySet = m.keySet();
for (Integer key : keySet) {
String value = m.get(key);
System.out.println(key + "," + value);//1,张思杰2,/张思涵3,/刘老六
}
//以Map集合的键作为List集合的参数类型,获取Map集合的键集合;
List<String> valueList = new ArrayList<>(m.values());
for (String s : valueList) {
System.out.print(s + ", ");//张思杰, 张思涵, 刘老六,
}
//以Map集合的值作为List集合的参数类型,获取Map集合的值集合;
List<Integer> keyList = new ArrayList<Integer>(m.keySet());
for (Integer i : keyList) {
System.out.print(i + ", ");//1, 2, 3,
}
}
4, Array(数组)转换为Set:
String [] countries = {"India", "Switzerland", "Italy"};
Set<String> set = new HashSet<String>(Arrays.asList(countries));
System.out.println(set);
5.Map转换为Set:
参见第3条;
继承(extends)
1: 继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及 追加属性和方法.
this关键字:
1: 表示类中属性,如下代码是为了明确的表示出,哪个是类中的属性,于是就要加上"this.属性名称",加以区分!
2: this修饰的变量指代成员变量,用于区分成员变量和局部变量;
可以使用this关键字调用本类中的构造方法
3:如果在类中有多个构造方法的话,可以利用this关键字互相调用.
4: this()调用构造方法的话,必须放在构造方法的首行!并且this(参数)也只能放在构造方法首行,放在其他方法首行也会报错!
5: 调用本类中的构造方法时,不能自己调用自己,至少有一个构造方法不用this()调用,不然会出现递归或者死循环!
this调用构造方法的实例:
asdasd类:
public class asdasd {
private String name;
private int age;
public asdasd() {
System.out.println("无参构造!");
}
public asdasd(String name, int age) {
this(name);
this.name = name;
this.age = age;
System.out.println(name);
}
public asdasd(String name) {
System.out.println("含有一个参数name的构造方法!");
this.name = name;
}
public asdasd(int age) {
this();
this.age = age;
System.out.println(age);
}
}
测试类:
public class Demo {
public static void main(String[] args) {
asdasd a = new asdasd("张思杰", 22);
}
}
运行结果:
含有一个参数name的构造方法!
张思杰
由此可见,this在类中调用了本类的其他构造方法.
super
1: 在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句,super();
super()语句:会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();
注意:当子类的双参中含有两个参数的super时,子类双参在初始化时调用的是父类的双参构造;
子类双参构造:
public Two(String name, int age) {
super(name, age);
System.out.println("这是子类双参构造!");
}
测试类创建对象:
Two t = new Two("张思佳", 22);
System.out.println(t);
输出:
这是父类双参构造!
这是子类双参构造!
Random.Two@15db9742
但是:当子类的双参构造不含有super时,第一行会有一条隐式的语句,即super();即使初始化调用的是子类的双参,但子类的双参调用的却是父类的无参!!!
子类不含super双参构造:
public Two(String name, int age) {
System.out.println("这是子类双参构造!");
}
测试类:
public static void main(String[] args) {
Two t = new Two("张思佳", 22);
System.out.println(t);
}
输出:
这是父类无参构造!
这是子类双参构造!
Random.Two@15db9742
2: >>为什么子类一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。如果要访问父类中制定的构造函数,可以通过手动定义super()语句的方式指定。比如父类中有Fu(String name){}这样的构造函数,那么子类在访问这样的构造函数时,就不能使用隐式的super()来访问父类中的该构造函数,而必须用显式的super(String name)语句来访问。父类不是隐式的,那么子类在访问时,super语句也不能是隐式的。
3: 子类中所有的构造函数都会默认访问父类中空参数的构造函数。因为子类每一个构造函数内的第一行都有一句隐式super();
4: 当父类中没有空参数的构造函数时,子类必须手动通过super或者this语句形式来指定要访问的构造函数。
5: 当然,子类中的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少会有一个构造函数来访问父类中的构造函数。
this,super的区别:
1: this 访问本类中的属性,如果本类中没有找到此属性,则从父类中继续查找。
super 访问父类中的属性。
2: this 访问本类中的方法,如果在本类中没有此方法,则在父类中继续查找。
super 直接访问父类中的方法。
3: this 调用本类中的构造方法,必须放在构造方法首行,他是根据不同参数个数,调用对应本类的不同参数个数的构造方法。
4: 注:对于this 和super本身都可以调用构造方法,而且调用的时候都必须放在构造方法的首行,所以这两个关键字肯定不能同时出现!
注意:
无论在main()中子类实例化过程中,它括号中的参数有几个(S s = new S(“nishuibaichuan”,21); ),他都会调用父类中的无参构造方法!!!!
方法重写
1、方法重写概念
子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
2、方法重写的应用场景
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了 父类的功能,又定义了子类特有的内容
3、Override注解
用来检测当前的方法,是否是重写的方法,起到【校验】的作用
注意:
1: java中只支持单继承,不支持多继承.
权限修饰符
final关键字
fianl关键字的作用:
1: final代表最终的意思,可以修饰成员方法,成员变量,类
fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
final修饰方法:该方法不能被重写
final修饰变量:表明该变量是一个常量,不能再次赋值
2: final修饰局部变量
fianl修饰基本数据类型变量 —> 基本类型的数据值不能发生改变
final修饰引用数据类型变量 —> 引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的!!!
static修饰符
1: static关键字是静态的意思,可以修饰【成员方法】,【成员变量】
2: 被类的所有对象共享,这也是我们判断是否使用静态关键字的条件
3: 可以通过类名调用,当然也可以通过对象名调用【推荐使用类名调用】.
4:static的访问特点
–非静态的成员方法
=== 能访问静态的成员变量
===能访问非静态的成员变量
=== 能访问静态的成员方法
=== 能访问非静态的成员方法
– 静态的成员方法
===能访问静态的成员变量
=== 能访问静态的成员方法
–总结成一句话就是:
静态成员方法只能访问静态成员
多态
1: 前提:
要有继承或实现关系
要有方法的重写
要有父类引用指向子类对象: 父类() a = new 子类()
2: 多态就是对同一个对象,在不同时刻表现出来的不同形态
3: 多态的成员特点是什么
成员变量:编译看左边、运行看左边
成员方法:编译看左边、运行看右边
4:理解:
动物类(父类):
public class Animal {
void eat() {
}
}
猫类(子类):
public class Cat extends Animal {
@Override
void eat() {
System.out.println("猫吃鱼!");
}
public void run() {
System.out.println("猫跑步!");
}
}
狗类(子类):
public class Dog extends Animal {
@Override
void eat() {
System.out.println("狗吃肉!");
}
public void skip() {
System.out.println("狗游泳!");
}
}
测试类:
public class Demo {
public static void main(String[] args) {
Animal a = new Cat();
a.eat();
((Cat) a).run();
//此处虽然猫狗类中有自己特有的方法,但是创建对象用的是父类多态的初始化,
//所以想要实现子类特有方法,必须强转!!
Animal b = new Dog();
b.eat();
((Dog) b).skip();
}
输出:
猫吃鱼!
猫跑步!
狗吃肉!
狗游泳!
明明初始化的是同一个Animal对象,但是体现出来的方法和结果却截然不同,这是因为在不同的环境下,Animal体现出了不同的形态,这就是多态!
多态的好处和弊端:
好处:
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
弊端:
不能使用子类的特有成员
多态向上转型与向下转型:
//向上转型
Animal a = new Cat();
a.eat();
//向下转型
Cat c = (Cat)a;
c.eat();
c.playGame();
抽象类
抽象类的特点
1,抽象方法一定在抽象类中。
2,抽象方法和抽象类都必须被abstract关键字修饰。
3,抽象类不可以用new创建和实例化对象。因为抽象类本身就是不完整的。
4,抽象类中的抽象方法要被使用,必须由子类复写所有的抽象方法后,建立子类对象调用。
//抽象类的定义
public abstract class 类名 {}
//抽象方法的定义
public abstract void eat();
5, 抽象类不能实例化 抽象类如何实例化呢?
—参照多态的方式,通过子类对象实例化,这叫抽象类多态
6, 抽象类的子类 要么重写抽象类中的所有抽象方法 要么是抽象类
接口
public interface 接口名 {}
1, 接口的子类
- 要么重写接口中的所有抽象方法 要么子类也是抽象类
2, 接口的成员特点 - 成员变量 只能是常量 默认修饰符:public static final
- 构造方法 没有,因为接口主要是扩展功能的,而没有具体存在
- 成员方法 只能是抽象方法 默认修饰符:public abstract
3, Java为什么需要接口 - 通过接口实现多继承
- 用于实现耦合
- 如下图所示,一个类扩展了另一个类,一个接口扩展了另一个接口,一个类实现了一个接口。
4, 一个类只能继承一个直接的父类,但可以实现多个接口,间接的实现了多继承
.
5, 接口中的方法默认都是public,abstract类型的(都可省略),没有方法体,不能被实例化,想要实例化接口中的抽象方法,必须通过接口的实现类,然后在测试程序中实例化接口的实现类,从而实现接口抽象方法的实例化!
6, 类与类的关系
- 继承关系,只能单继承,但是可以多层继承
7, 类与接口的关系
- 实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
8, 接口与接口的关系
- 继承关系,可以单继承,也可以多继承
参数传递
实例:
public class Test {
public static void changeValue(int i) {
i=2;
System.out.println("during test: i = " + i);
}
public static void main(String[] args) {
int i = 1;
System.out.println("Before test: i = " + i);
changeValue(i);
System.out.println("After test: i = " + i);
}
}
运行结果:
Before test: i = 1
during test: i = 2
After test: i = 1
虽然在 changeValue(int i)方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响。其内部原理是,main方法里的变量和changeValue方法的参数是两个不同的变量,以参数形式传递简单类型的变量时,实际上是将参数的值作了一个拷贝传进方法的,那么在方法里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。
public class Test {
public static void test(StringBuffer str) {
str.append(", World!");
}
public static void main(String[] args) {
StringBuffer string = new StringBuffer("Hello");
test(string);
System.out.println(string);
}
}
运行结果:
Hello, World!
似乎变量string被“改变”了。但其实改变的并不是string变量本身,也就是说string保存的内存地址并没有被改变,改变的是它所指向的对象实例。内部原理是这样的,在main方法里定义了一个对象引用string,并且把它和一个对象实例关联new StringBuffer。方法调用的时候,string所保存的对象实例的内存地址传递给了test方法的对象引用参数str,这时就有两个对象引用变量指向同一个对象实例。这两个对象引用都可以对该对象实例进行操作,操作结果都有效,因此在test方法执行完之后,对象实例的内容已经被改变了,这个时候再通过main方法里的string引用去查看对象实例的内容,看到的就是改变之后的内容。
总结:
在方法中修改基本数据类型的变量,并不会改变其具体值;
在方法中修改引用类型的值,不会改变的是其地址值,而其数值则会发生改变!
类名作为形参:
- 方法的形参是类名,其实需要的是该类的对象 实际传递的是该对象的【地址值】
定义一个猫类:
public class Cat {
public void eat() {
System.out.println("猫吃鱼!");
}
}
定义一个新的类:
public class CatDemo {
public void run(Cat c) {
c.eat();
}
public void sho() {
Cat c = new Cat();
c.eat();
}
}
由上例可知,本来在Cat Demo类的方法中想要调用Cat中的eat方法需要创建Cat对象,但是利用类名作为方法的形参后,便无需实例化对象,这是因为类名作为方法的形参,本身需要的就是该类的对象;
类名作为方法的返回值:
- 方法的返回值是类名,其实返回的是该类的对象 实际传递的,也是该对象的【地址值】
public Cat getCat() {
Cat c = new Cat();
return c;
}
抽象类作为形参:
- 方法的形参是抽象类名,其实需要的是该抽象类的子类对象
动物类:
abstract class Animal {
public abstract void eat();
}
猫类:
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
class AnimalOperator {
public void useAnimal(Animal a) { //Animal a = new Cat();
a.eat();
}
抽象类作为返回值:
- 方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
public Animal getAnimal() {
Animal a = new Cat();
return a;
}
接口作为形参和返回值:
- 方法的形参是接口名,其实需要的是该接口的实现类对象
- 方法的返回值是接口名,其实返回的是该接口的实现类对象
内部类
1, 在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
2, 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是内部类的名称为外部类名$内部类名。
3, Java在创建内部类对象时,会隐式的把其外部类对象的引用传给内部类并保存,这样就使得内部类对象始终都可以访问其外部类对象,同时这也是为什么在外部类作用范围之外要创建内部类对象必须创建其外部类对象。
4, 如果内部类声明为静态类,那么内部类就不能随便访问外部类的成员变量,仍然只能访问外部类的静态成员变量。
5, 外部类只能使用public和缺省的包访问权限修饰类,而内部类可以使用public,protected,private访问权限来修饰类。
6, 内部类可以直接访问外部类的成员,包括私有 外部类要访问内部类的成员,必须创建对象。
(一) 成员内部类:
1, 作为外部类的一个成员存在,与外部类的属性、方法并列。
2, 当内外部类中成员变量同名后,访问的是内部类的成员变量
3, 当内外部类中成员变量同名后,可以通过外部类名.this来访问外部类成员变量
- System.out.println(OuterA.this.ob)
4, 外界创建成员内部类格式 格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象; 举例:
- Outer.Inner oi = new Outer().new Inner();
5, 当然如果内部类为静态内部类,那就不需要创建外部类对象了
6, 成员内部类的推荐使用方案 将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有 化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用
7, 注:内部类是一个编译时的概念,一旦编译成功,就会有两个完全不同的类OuterA.class和OuterA$InnerA.class
(二) 局部/方法内部类:
1, 局部内部类是在方法中定义的类
public class Outer {
private String name;
private static int age;
public void show() {
final String address = "西安";//局部变量必须为final类型
class Inter {
public String hobby;
private void method() {
String name = "张思杰";//可以直接访问外部类的成员变量
int age = 22;
System.out.println(address);//可以直接访问其方法的局部变量
System.out.println("内部类方法!");
}
}
Inter i = new Inter();//方法内部类只能在定义其内部类的方法中实例化
}
}
注:方法内部类在定义该类的方法中实例化,不能再此方法外对其实例化。方法内部类对象不能使用该内部类所在方法的非final局部变量。
(三) 匿名内部类:
1, 顾名思义就是没有名字的内部类。
2, 匿名内部类的前提
首先匿名内部类要继承自抽象基类或者实现基类接口
3, 匿名内部类的格式 格式:
new 类名 ( ) { 重写方法 } new 接口名 ( ) { 重写方法 }
4,使用匿名内部类注意一下几点:
- 匿名内部类不能有构造方法。
- 匿名内部类不能定义任何静态成员、方法和类。
- 匿名内部类不能是public,protected,private,static。
- 只能创建匿名内部类的一个实例。
- 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
- 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
用接口实现匿名内部类:
接口:
interface Inter{
void method();
}
类:
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
用继承父类实现匿名内部类:
abstract class Apple{
public abstract void eat () ;
}
public class niming {
public static void main(String[] args) {
new Apple() {
public void eat() {
System.out.println("apple");
}
}.eat();
}
}
(四) 静态内部类:
1, 静态内部类又叫嵌套类,静态内部类不像普通内部类对象隐式地保存了外部类的一个引用,他们只是一种共享关系。
2, 使用Static修饰的内部类注意以下几点:
(1) 要创建内部类对象,并不需要外部类对象。
(2) 不能从内部类对象中访问外部类的非静态成员(包括非静态成员变量,非静态方法)。
package com.nbl;
外部类:
public class OuterD {
public String oa = "oa";
public static String ob = "ob";
public static String oc = "oc";
静态内部类:
static class InnerD{
private String ia = "ia";
private String oc = "ic";
private static String id = "id";
public void doMethod(){
//静态内部类不能访问外部类非静态成员
//System.out.println(oa);
//静态内部类直接访问外部类的静态成员
System.out.println(ob);
//静态内部类直接访问内部类变量
System.out.println(ia);
//静态内部类和外部类的静态成员同名,可以通过外部类名加成员名访问
System.out.println(OuterD.oc);
}
}
// 外部类方法访问内部类:
public void doAction(){
//外部类访问内部类静态成员可以直接通过类名来访问
System.out.println(InnerD.id);
//外部类访问内部类非静态成员需要实例化内部类
System.out.println(new InnerD().ia);
}
public static void main(String[] args) {
//外部类执行方法
OuterD od = new OuterD();
od.doAction();
// 静态内部类的对象可以直接生成
// Outer.Inner in = new Outer.Inner();
// 而不需要通过生成外部类对象来生成。
InnerD id = new InnerD();
id.doMethod();
}
}
注:静态内部类可以看成是一个高级类来使用,他并不依赖外部类对象。
常用API
Math:
System:
Object:
1, java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。, 在对象实例化的时候,最终找的父类就是Object。
2, 如果一个类没有特别指定父类, 那么默认则继承自Object类。
3, public String toString():返回该对象的字符串表示。
- toString方法返回该对象的字符串表示,其实该字符串内容就是对象的类型+@+内存地址值。
由于toString方法返回的结果是内存地址,而在开发中,经常需要按照对象的属性得到相应的字符串表现形式,因此也需要重写它。
public class Person {
private String name;
private int age;
//如果不希望使用toString方法的默认行为,则可以对它进行覆盖重写。例如自定义的Person类:
@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
包装类
1: 基本类型包装类的作用:
- 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据
- 常用的操作之一:用于基本数据类型与字符串之间的转换
Integer类:
1, 包装一个对象中的原始类型 int 的值
2, Integer类构造方法
int和String类型的相互转换
1, int转换为String :
- 方式一:直接在数字后加一个空字符串
- 方式二:通过String类静态方法valueOf()
public class Demo {
public static void main(String[] args) {
int i = 10;
// 方式一
String s1 = i + "";
System.out.println(s1);
// 方式二
String s2 = String.valueOf(i);
System.out.println(s2);
}
}
2, String 转换为int:
- 方式一:先将字符串数字转成Integer,再调用valueOf()方法
- 方式二:通过Integer静态方法parseInt()进行转换
public static void main(String[] args) {
String s = "10";
//方式一:
Integer i = Integer.valueOf(s);
int a = i.intValue();
System.out.println(a);
//方式二:
int b = Integer.parseInt(s);
System.out.println(b);
}
时间日期类
Date类:
public static void main(String[] args) {
Date d1 = new Date();
System.out.println(d1);
// Sat Feb 15 14:30:28 CST 2020
//从标准基准时间起 指定的毫秒数
long date = 1000 * 60 * 60;
Date d2 = new Date(date);
System.out.println(d2);
//Thu Jan 01 09:00:00 CST 1970
}
SimpleDateFormat类:
该类主要用于格式化和解析日期.
public static void main(String[] args) throws ParseException {
Date d = new Date();
// 从Date到String
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);// 2020年02月15日 00:45:29
// 从String到Date
String ss = "2020-02-13 00:12:20";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf2.parse(ss);
System.out.println(dd);//Thu Feb 13 00:12:20 CST 2020
}
Calendar类:
public static void main(String[] args) {
// 获取日历对象
Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
// 2020年2月15日
// 根据日历的规则,将指定的时 间量添加或减去给定的日历字段
// 求三年前的今天:
c.add(Calendar.YEAR, -3);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
//2017年2月15日
// 设置当前日历的年月日
c.set(2050, 12, 03);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
//2051年1月3日
}
异常
1,异常体系:
2, Java异常是Java提供的一种识别及响应错误的一致性机制。
==Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答3个问题:
- 异常类型回答了“什么”被抛出
- 异常堆栈跟踪回答了“在哪“抛出
- 异常信息回答了“为什么“会抛出。
3,Java异常机制用到的几个关键字:try、catch、finally、throw、throws
• try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
• catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
• finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
• throw – 用于抛出异常。
• throws – 用在方法签名中,用于声明该方法可能抛出的异常。
编译时异常和运行时异常的区别:
编译时异常
- 都是Exception类及其子类
- 必须显示处理,否则程序就会发生错误,无法通过编译
运行时异常
- 都是RuntimeException类及其子类
- 无需显示处理,也可以和编译时异常一样处理
注意事项
- 编译时异常必须要进行处理,两种处理方案:try…catch …或者 throws,如果采用 throws 这种方案,将来谁调用谁处理
- 运行时异常可以不处理,出现问题后,需要我们回来修改代码
泛型
1. 泛型的优点:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
- 如果我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。
- 提高了工具类的使用范围
2. 泛型类:
格式:
修饰符 class 类名<类型> { }
public class Generic<T> {
什么时候定义泛型类
-
当类中要操作的引用数据类型不确定的时候。
-
早期定义Object来完成扩展。现在定义泛型来完成扩展。
3. 泛型方法:
格式:
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
public <T> void show(T t) {
注意:
-
静态方法不可以访问类上定义的泛型。
-
如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
public class Test<T> {//类的泛型为T
public T show(T t) {//普通方法直接访问类定义的泛型
return t;
}
public static T method(T t) {//静态方法不能访问类定义的泛型,这里出现了报错!
System.out.println(t);
}
public static <T> void run(T t) {// 如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
System.out.println(t);
}
}
4. 泛型接口:
格式:
//修饰符 interface 接口名<类型> { }
public interface Generic<T> {
void show(T t);
}
5. 类型通配符:
1. 上下限:
-
类型通配符上限:<? extends 类型>
List<? extends Number> //它表示的类型是Number或者其子类型 -
类型通配符下限:<? super 类型>
List<? super Number> //它表示的类型是Number或者其父类型
2. ?通配符与T的区别:
-
T:主要用于泛型接口,类,方法定义的时候,作用只是将参数泛型化(类方法的定义)。
-
?:主要用于实例化对象的时候,当我们实例化对象,不确定泛型参数的具体类型的时候,可以使用通配符进行对象从属范围定义。
3. 概念:
- List<?> 在逻辑上是List、List 等所有List<具体类型实参>的父类。
4. 例子:
public static void main(String[] args) {
// 类型通配符:<?>
List<?> list1 = new ArrayList<Integer>();
List<?> list2 = new ArrayList<String>();
List<?> list3 = new ArrayList<Character>();
// 类型通配符上限:<? extends 类型>
List<? extends Number> list4 = new ArrayList<Number>();
List<? extends Number> list5 = new ArrayList<Integer>();
List<? extends Number> list6 = new ArrayList<Object>();
// 这里会报错,因为Object超过了类型通配符定义的Number上限!
// 类型通配符下限:<? super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
}
可变参数
-
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
修饰符 返回值类型 方法名(数据类型… 变量名) { } -
Arrays工具类中有一个静态方法
- public static List asList(T… a):返回由指定数组支持的固定大小的列表
- 返回的集合不能做增删操作,可以做修改操作
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "java");
// list.add("web");//不能做增删操作
// list.remove(2);//不能做增删操作
list.set(1, "web");
}
- 举例:
package Test;
//可变参数实际上就是一个数组,传递多种类型的参数时,可变参数一定需要放在最后。
public class Var {
public static void main(String[] args) {
System.out.println(add("hello"));
System.out.println(add("SSSS",2,23,3));//传递任意数量的值
System.out.println(add( "hhh",new int[]{1,2,3,4} ));//接收数组
}
public final static int add(String str,int...data){
int result=0;
for (int i=0;i<data.length;i++){
result=data[i]+result;
}
return result;
}
}
File类
1. File类介绍:
- 它是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径而已。 可以是存在的,也可以是不存在的。将来是要通过具体的操作把这个路径的内容转换为具体存在的
2. File类的构造方法:
- File(String pathname)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File f1 = new File("E:\\itcast\\java.txt");
- File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例
File f2 = new File("E:\\itcast","java.txt");
- File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例
File f3 = new File("E:\\itcast");
File f4 = new File(f3,"java.txt");
3.File类方法:
- public booleancreateNewFile()
当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件
File f1 = new File("E:\\JAVA SE\\java.txt");
System.out.println(f1.createNewFile());// true
- public boolean mkdir()
创建由此抽象路径名命名的目录
File f2 = new File("E:\\JAVA SE\\javaSE");
System.out.println(f2.mkdir());//true
- public boolean mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
File f3 = new File("E:\\JAVA SE\\javaweb\\java");
System.out.println(f3.mkdirs());//true
4. File类判断和获取功能
- public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
- public boolean isFile() 测试此抽象路径名表示的File是否为文件
- public boolean exists() 测试此抽象路径名表示的File是否存在
- public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
- public String getPath() 将此抽象路径名转换为路径名字符串
- public String getName() 返回由此抽象路径名表示的文件或目录的名称
- public String[] list() 返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
- public File[] listFiles() 返回此抽象路径名表示的目录中的文件和目录的File对象数组
- public boolean delete() 删除由此抽象路径名表示的文件或目录
递归
1. 递归介绍:
- 以编程的角度来看,递归指的是方法定义中调用方法本身的现象
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
- 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算
- 递归一定要有出口。否则内存溢出
- 递归虽然有出口,但是递归的次数也不宜过多。否则内存溢出
- 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。
2. 递归遍历目录下的所有内容:
public class Demo {
public static void main(String[] args) {
File file = new File("E:\\JAVA SE");
//调用方法
get(file);
}
//定义一个方法,用于获取给定目录下所有内容
public static void get(File file) {
//获取给定的File目录下所有的文件或者目录的File数组
File[] f = file.listFiles();
//遍历该File数组,得到每一个File对象
if(fileArray != null) {
for (File ff : f) {
//判断该File对象是否是目录
if (ff.isDirectory()) {
//是:递归调用
get(ff);
} else {
//不是:获取绝对路径输出在控制台
System.out.println(ff.getAbsolutePath());
}
}
}
}
IO流
1. 介绍:
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
2. IO流分类:
-
按照数据的流向
输入流:读数据
输出流:写数据 -
按照数据类型来分
字节流
-----字节输入流
-----字节输出流
–
字符流
-----字符输入流
-----字符输出流
3. IO流的使用场景:
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件。优先使用字节流
- 如果不确定文件类型,优先使用字节流。字节流是万能的流
4. 字节流写数据(字节输出流):
- 字节流抽象基类:
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 字节输出流:
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
- 实例:
//创建文件输出流以指定的名称写入文件
FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt");
//将指定的字节写入此文件输出流
fos.write(97);
//关闭此文件输出流并释放与此流相关联的任何系统资源。
fos.close();
- 字节流写数据的三种方式:
- void write(int b)
将指定的字节写入此文件输出流 一次写一个字节数据
fos.write(97);
- void write(byte[] b)
将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据
byte[] bys = {97, 98, 99, 100, 101};
fos.write(bys);//abcde
- void write(byte[] b, int off, int len)
将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据
byte[] bys = { 97, 98, 99, 100 };
fos.write(bys, 1, 3);//bcd
5. 字节流写数据的两个问题:
- 字节流写数据如何实现换行
- windows:\r\n
- linux:\n
- mac:\r
- 字节流写数据如何实现追加写入
- public FileOutputStream(String name,boolean append)
- 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("E:\\JAVA SE\\java.txt");
byte[] bys = { 97, 98, 99, 100 };
fos.write(bys, 1, 3);//bcd
fos.close();
FileOutputStream fos1 = new FileOutputStream("E:\\JAVA SE\\java.txt", true);
//若为true,则在文件末尾追加输入;若为false,则清空文件重新输入;
byte[] byss = { 97, 98, 99, 100 };
fos1.write(byss);//bcdabcd
fos1.close();
}
6. 字节流读数据(字节输入流):
- 字节输入流
- FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name命名
- 字节输入流读取数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
FileInputStream fis = new FileInputStream("myByteStream\\fos.txt");
int by;
while ((by=fis.read())!=-1) {
System.out.print((char)by);
}
//释放资源
fis.close();
}
}
- 字节流复制文本文件(一次读取一个字节,一次写入一个字节)
FileInputStream fis = new FileInputStream("E:\\itcast\\窗里窗外.txt");
FileOutputStream fos = new FileOutputStream("myByteStream\\窗里窗 外.txt");
int by;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fis.close();
fos.close();
- 字节流读数据(一次读一个字节数组数据)
byte[] bys = new byte[1024];
int len;
while ((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();
}
}
7. 字节缓冲流
- 字节缓冲流介绍
- lBufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
- lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
- 方法:
public static void main(String[] args) throws IOException {
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:\\JAVA SE\\java.txt"));
//输入数据
bos.write("Hello\r\n".getBytes());
bos.write("World\r\n".getBytes());
bos.close();
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\JAVA SE\\java.txt"));
//一次读取一个字节
int by;
while ((by = bis.read()) != -1) {
System.out.print((char) by);
}
//一次读取一个字节数组
byte[] bys = new byte[1024];
int len;
while ((len = bis.read(bys)) != -1) {
System.out.println(new String(bys, 0, len));
}
bis.close();
}
- 对于文件,图片等的复制,速度最快的为字节缓冲流,且读数据时最快的方法为一次读取一个字节数组;
8. 字符流
- 字符流的编码解码问题:
public static void main(String[] args) throws UnsupportedEncodingException {
String s = "张思杰";
byte[] bys = s.getBytes();
System.out.println(Arrays.toString(bys));// [-43, -59, -53, -68, -67, -36]
byte[] by = s.getBytes("UTF-8");
System.out.println(Arrays.toString(by));// [-27, -68, -96, -26, -128, -99, -26, -99, -80]
//用什么格式编码,就必须以什么格式解码
String ss = new String(bys);
System.out.println(ss);// 张思杰
String sss = new String(by, "UTF-8");
System.out.println(sss);// 张思杰
}
- 字符流中和编码解码问题相关的两个类
-
InputStreamReader:
@ 是从字节流到字符流的桥梁
@ 它读取字节,并使用指定的编码将其解码为字符
@ 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集 -
OutputStreamWriter:
@是从字符流到字节流的桥梁
@使用指定的编码将写入的字符编码为字节
@它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
- 构造方法:
public static void main(String[] args) throws IOException {
//可以按默认格式创建字符输出流对象,也可指定格式
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JAVA SE\\java.txt"));//("E:\\...."),"GBK");
osw.write("张思杰");
osw.close();
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\JAVA SE\\java.txt"),"GBK");
//一次读取一个字符数据
int i;
while ((i = isr.read()) != -1) {
System.out.print((char) i);
}
isr.close();
}
9. 字符流读写数据的五种方式:
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\JAVA SE\\java.txt"));
// 写一个字符
osw.write("张思杰\r\n");
osw.flush();
// 写一个字符数组
char[] c = { 'a', 'b', 'c', 'd' };
osw.write(c);
// osw.flush();
// 写入字符数组的一部分
osw.write(c, 1, 3);
// osw.flush();
// 写一个字符串
osw.write("abcde\r\n");
// osw.flush();
// 写一个字符串的一部分
osw.write("abcdefghijk\r\n", 2, 6);
// osw.flush();
osw.close();
}
注意:
刷新流可以不用写,在最后的一个关闭流关闭之前会自动进行一次刷新,但关闭之后便不可在写入任何数据!
10. 字符流读数据的两种方式:
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\JAVA SE\\java.txt"));
// 一次读一个字符数据
int i;
while ((i = isr.read()) != -1) {
System.out.print((char) i);
}
// 一次读取一个字符串数组数据
char[] ch = new char[1024];
int len;
while ((len = isr.read(ch)) != -1) {
System.out.println(new String(ch, 0, len));
}
isr.close();
}
便捷版写法:
//根据数据源创建字符输入流对象
FileReader fr = new FileReader("myCharStream\\ConversionStreamDemo.java");
//根据目的地创建字符输出流对象
FileWriter fw = new FileWriter("myCharStream\\Copy.java");
11. 字符缓冲流:
- 构造方法:
//创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\bw.txt"));
//创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("myCharStream\\bw.txt"));
剩下的输入输出和字符流一致
- 特有方法:
//写一行行分隔符,行分隔符字符串由系统属性定义
bw.newLine();
//读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null
bw.write(line);
- 利用字符缓冲流的特殊方法复制文件:
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("E:\\JAVA SE\\java.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\zsj.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
12. IO流小结:
字节流:
字节流可以复制任意文件数据,有四种方式,一般采用字节缓冲流一次读取一个字节数组的方式;
字符流:
字符流只能复制文本数据,有五种方式,一般采用字符缓冲流的特有功能
13. 对象序列化流和反序列化流:
- 什么是对象序列化:
- 实例:
- 首先创建一个自定义的类,这里不在赘述
- 对象序列化:
public static void main(String[] args) throws FileNotFoundException, IOException {
//创建存储对象的输出流对象:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\JAVA SE\\java.txt"));
Student s = new Student("张思杰", "22");
//将对象的信息写入指定的文件中
oos.writeObject(s);
oos.close();
}
- 对象反序列化:
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\JAVA SE\\java.txt"));
//读取文件中的对象信息:
Object o = ois.readObject();
//将读取到的信息强转为自定义的类类型:
Student s = (Student) o;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
注意事项:
- 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
14. serialVersionUID和transient
学生类:
public class Student implements Serializable {
//给ID
private static final long serialVersionUID = 2l;
private String name;
private String age;
private transient int id;//给该变量添加了transient后,该变量将不参加序列化,读取时将输出该变量的初始值,例如int类型就输出0;
//省略了构造方法和get,set 方法......
// @Override
// public String toString() {
// return name + age;
}
写入类:
public static void main(String[] args) throws FileNotFoundException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\JAVA SE\\java.txt"));
Student s = new Student("张思杰", "22", 06171001);
oos.writeObject(s);
oos.close();
}
读文件类:
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\JAVA SE\\java.txt"));
Object o = ois.readObject();
Student s = (Student) o;
System.out.println(s.getName() + "," + s.getAge() + "," + s.getId());
ois.close();
}
- 当我们把一个对象序列化后,它将被写在一个文件中,即使JVM停止运行,下次在读取该文件时,便可以重新得到该对象,就像游戏的保存存档一样,
- 这里的原理是:当我们把该对象写入文件时,系统会保存一个该对象所属的类的类似于ID的值,并给文件也赋其值,下次读取文件时,系统会判断与文件ID值对应的类的ID值,
- 但是如果我们在第一次把对象序列化进文件后,修改了该对象所属的类(例如在其中添加了toString方法//但不得修改变量,参数等),该类的ID值就会发生改变,可是文件的ID值却还是以前的ID值,所以在反序列化时便会报错;
- 所以我们可以在定义类的时候,给该类一个确定的ID值"private static final long serialVersionUID = 2l;"这样,即使后面改变了该类,系统也无法修改该类的ID值!!再次反序列化便不会报错了.
线程
1. 实现多线程:
-
进程:
是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源 -
线程:
是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
-
实现多线程的方式:
继承Thread类,并重写run()方法;
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();//导致此线程开始执行;
//Java虚拟机调用此线程的run方法
my1.start();
- run方法和start方法:
- run()方法是用来封装被线程执行的代码的
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程;然后由JVM调用此线程的run()方法
- 线程的一些方法:
自定义类:
public class MulException extends Thread {
public MulException() {
}
public MulException(String name) {
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//getName返回此线程的名称
System.out.println(getName() + ":" + i);
}
}
}
测试类:
public static void main(String[] args) {
//也可直接调用带参构造方法
// MulException m1 = new MulException("高铁");
MulException m1 = new MulException();
MulException m2 = new MulException();
// 更改此线程的名称
m1.setName("飞机");
m2.setName("高铁");
m1.start();
m2.start();
// 返回正在执行当前线程的对象(即main方法)
System.out.println(Thread.currentThread().getName());
// main
// 高铁:0
// 高铁:1
// 高铁:2
// 高铁:3
// ....
}
- 线程调度:
- 两种调度方式:
a. 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
b. 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些**(Java使用的是抢占式调度模型)**
-
随机性:
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的 -
优先级方法:
public static void main(String[] args) {
MulException m1 = new MulException();
MulException m2 = new MulException();
m1.setName("飞机");
m2.setName("高铁");
// 返回此线程的优先级
System.out.println(m1.getPriority());
System.out.println(m2.getPriority());
// 更改此线程的优先级
m1.setPriority(10);
m2.setPriority(1);
m1.start();
m2.start();
}
- 线程控制方法:
- 停留线程:
public class MulException extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
try {
// 使当前正在执行的线程停留(暂停执行)指定的毫秒数
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 等待线程死亡(执行完毕):
m1.start();
try {
m1.join();//等待这个线程死亡
} catch (InterruptedException e) {
e.printStackTrace();
}
m2.start();
m3.start();
//这里在m1执行完毕后才会继续执行m2,m3;
- 守护线程:
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
也就是说,守护线程会在主线程运行结束后,陆续结束掉
- 线程的生命周期:
线程一共有五种状态,线程在各种状态之间转换
- 实现多线程的方式二:实现Runnable接口
public class MulException implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
-----------------------------------------------------------
public static void main(String[] args) {
MulException m = new MulException();
Thread t1 = new Thread(m, "飞机");
Thread t2 = new Thread(m, "高铁");
t1.start();
t2.start();
}
总结:
-
多线程的实现方案有两种
a. 继承Thread类
b. 实现Runnable接口 -
相比继承Thread类,实现Runnable接口的好处
a. 避免了Java单继承的局限性
b. 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
2. 线程同步:
- 数据安全问题:
-
安全问题出现的条件
是多线程环境
有共享数据
有多条语句操作共享数据 -
解决方法:
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
Java提供了同步代码块的方式来解决
- 同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
- 同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
票类:
public class Sell implements Runnable {
private int ticket = 100;
private Object b = new Object();
@Override
public void run() {
while (true) {
synchronized (b) {//给下面步骤加了锁,一次便只能进来一个线程,等他执行完毕,其他线程才会进入
if (ticket > 0) {
try {
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
Thread.sleep(10);
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
Sell s = new Sell();
Thread t1 = new Thread(s, "窗口1");
Thread t2 = new Thread(s, "窗口2");
Thread t3 = new Thread(s, "窗口3");
t1.start();
t2.start();
t3.start();
}
- 同步方法解决数据安全问题:
- 格式:
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
3. 线程的几个问题:
- 为什么调用strat();方法而不是直接调用run();方法:
- run()是顺序执行而start()则是并行执行。
- Thread.run():在当前线程中,直接调用run()方法、相当于调用普通方法(同步);
- Thread.start():将创建的线程加入到线程组,启动线程、需等到获取了时间片才执行(异步);
网络编程
网络编程三要素:
- IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识 - 端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识 - 协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
IP地址:
- IP地址分为两大类:
IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址“1100000010101000
00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题 - DOS常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
端口和协议:
-
端口号
用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败 -
协议
计算机网络中,连接和通信的规则被称为网络通信协议 -
UDP协议
a 用户数据报协议(User Datagram Protocol)
b. UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
c. 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
d. 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议 -
TCP协议
a. 传输控制协议 (Transmission Control Protocol)
b. TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
c. 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
d. 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
UDP通信:
发送数据:;
public class Test {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
} else {
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.43.243"), 54188);
ds.send(dp);
}
}
ds.close();
}
}
接收数据:
public class Demo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(54188);
while (true) {
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
ds.receive(dp);
System.out.println("数据是:" + new String(dp.getData(), 0, dp.getLength()));
}
}
}
Lambda表达式
使用Lambda表达式启动线程:
格式:
- (形式参数) -> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容组成Lambda表达式的三要素:
- 形式参数,箭头,代码块
public static void main(String[] args) {
new Thread(() -> {
System.out.println("多线程启动!");
}).start();
}
Lambda表达式的省略模式:
省略的规则
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
注意事项:
-
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
-
必须有上下文环境,才能推导出Lambda对应的接口
-
根据局部变量的赋值得知Lambda对应的接口
Runnable r = () -> System.out.println(“Lambda表达式”); -
根据调用方法的参数得知Lambda对应的接口
new Thread(() -> System.out.println(“Lambda表达式”)).start();
Lambda表达式和匿名内部类的区别:
所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
反射
Java代码在计算机中经历的三个阶段:
-
第一阶段(source源代码阶段):
创建class(类)生成一个 .java 文件,然后经过编译,会生成一个 .class 文件,此时这些代码并没有进入内存
通过类加载器,会把 .class文件加载进内存 -
第二阶段(Class类阶段):
在内存中利用 Class类 对象描述 .class 文件
-
第三阶段(运行时阶段):
new 一个新的对象
1. 基本概念:
- 反射就是把Java类中的各种成分映射成一个个的Java对象
- 将类的各个组成部分封装成其他对象,在内存中以一种新对象的形式存在,例如类的成员变量被封装成了Field[]
- 反射是一种间接操作目标对象的机制
- 核心是JVM在运行的时候才动态加载类
- 对于任意一个类,都能够知道这个类的所有属性和方法
- 调用这个方法/访问属性,不需要提前在编译期知道运行的对象是谁,反射机制允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性
- 程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态加载一些类,这些类之前用不到,所以没有加载到JVM,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载的
2. 反射原理:
- 获取class对象的方式:
1. 创建对象后获取内存中的class对象
//getClass方法在Object类中
Student s = new Student();
Class sc1 = s.getClass();// class practive.Student
2. 类名.class
Class sc2 = Student.class;
3. 将字节码文件加载进内存,返回Class对象
//多用于配置文件,将类名定义在配置文件中,读取文件,加载类
Class sc3 = Class.forName("practive.Student");
重要结论:同一个字节码文件(.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的class对象都是同一个
- Class对象的功能:
获取功能:
1. 获取成员变量们:
Class sc = Class.forName("practive.Student");
//第一种:public修饰的成员变量
Field[] fields = sc.getFields();
for(Field field : fields) {
System.out.println(field);
}//public java.lang.String practive.Student.a
//第二种:根据成员名获取指定的成员
Field a = sc.getField("a");
Student s = new Student();
Object object = a.get(s);//传一个对象进去,获取a的值
System.out.println(object);//null
a.set(s, "张思杰");//设置a的值
System.out.println(s);//Student [name=null, age=0, a=张思杰, b=null, c=null]
//第三种:获取所有成员,无所谓修饰符,在这里最高权限修饰符private也变得无效了
Field[] declaredFields = sc.getDeclaredFields();
for(Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//第四种:获取最高权限修饰符修饰的成员变量
Field d = sc.getDeclaredField("name");
//忽略访问权限修饰符的安全检查(暴力反射)
d.setAccessible(true);
Object object2 = d.get(s);//获取值
System.out.println(object2);//阿牛
}
2. 获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>...parameterTypes)
Constructor<T> getDeclaredConstructors(类<?>..parameterTypes)
3. 获取成员方法们:
public static void main(String[] args) throws Exception {
Class studentClass = Student.class;
Student s = new Student();
//获取成员方法,.invoke执行该方法
studentClass.getMethod("eat").invoke(s);
//获取带参的成员方法,并执行
studentClass.getMethod("eat", String.class).invoke(s, "你吃屎!");
//获取所有public修饰的方法,这里包括Object中的方法
Method[] methods = studentClass.getMethods();
for(Method method : methods) {
System.out.println(method);
System.out.println(method.getName());//获取方法名
}
}
4. 获取类名:
String className = studentClass.getName();
3. 反射的优缺点:
- 优点:
使用反射,我们就可以在运行时获得类的各种信息,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便快捷的创建灵活性高的代码 - 缺点:
反射会消耗一定的系统资源,因此,若不需要动态地创建一个对象,就不需要用反射
反射调用方法时可以忽略权限检查,可能破坏封装性导致安全问题
4. 反射的用途:
- 反编译: .class —> .java
- 通过反射机制访问Java对象的属性,方法,构造方法等