tags:
- 內部類
- 線程
- 進程
style: summer
JavaDay23 內部類和線程
@toc
一、複習
(一)Comparable 和 Comparator
兩者都是接口
- Comparable 是一個接口,給自定義類提供比較方式,遵從 Comparable的實現類,必須實現 compareTo(T o)方法;即進行兩個對象的比較;
- Comparator 是一個接口,用來做自定義比較器,遵從 Comparator接口的實現類,必須使用 compare(T o1, T o2);
方法1.實現自定義比較器
class MyCompare implements Comparator<Student>{
@override
public int compare(Student o1, Student o2){
return o1.getAge() - o2.getAge();
}
}
上面這種方式:好理解但是需要重新定義一個類,而且可能會統一放在一個包裏面,略顯複雜;
方法 2:使用匿名內部類的匿名對象
格式:Arrays.sort(T[] t, Comparator<? extends T> com)
Student[] array = {stu1, stu2, stu3};
Arrays.sort(array.new Comparator<Student>{
@Override
public int compare(Student o1, Student o2){
return o1.getAge() - o2.getAge();
}
});
二、內部類
-
生活實例:
在人類中,有些東西,比如內臟,用成員方法或者成員變量描述都顯着有點不太合適,因爲這些內臟首先是屬於【人體的一部分】,而且會使用【人體的一些屬性】,但是又擁有自己的一些【特徵】;能不能把這些器官,認爲是一個類,一個屬於人類內部的一個類。 -
內部類分類:
1.成員內部類
2.局部內部類
3.匿名內部類
(一)成員內部類
- 內部類和外部類比較:
- 1.成員內部類可以使用外部類的所有成員變量和成員方法, 不管用什麼權限修飾,不管是private還是public都可以使用,因爲,這是在內部類的內部使用。
- 2.【在Outer類的外部創建Outer的Inner對象】
格式如下:
外部類名.內部類名 內部類對象名 = new 外部類名().new 內部類名();
例如:Outer.inner inner = new Outer().new Inner();
【第一個知識點】:普通的成員變量和成員方法,在沒有對象的情況下,不能再類外使用 - 3.如果內部類和外部類存在同名的成員變量,這裏默認是就近原則,使用的是內部類的成員變量
如果想要使用外部類的成員變量和成員方法的格式:
外部類名.this.同名成員變量;
外部類名.this.同名成員方法(參數列表);
- 4.在外部類的類內方法中,可以創建內部類的對象
package com.qfedu.a_innnerclass;
class Outer {
int num = 100; //外部類的成員變量
private static int s = 10;
class Inner { //這是Outer類的一個成員內部類
int i = 10; //內部類的成員變量
int num = 50;
public void testInner() {
System.out.println("內部類的成員方法");
testOuter();
System.out.println("內部類同名成員變量:" + num); // 50 就近原則
//使用 外部類名.this.成員變量名 獲取同名外部成員變量
System.out.println("外部類同名成員變量:" + Outer.this.num);
//分別調用內部和外部成員方法
test();
Outer.this.test();
System.out.println(s);
testStatic();
}
public void test() {
System.out.println("內部類的test方法");
}
}//inner
public void testOuter() {
System.out.println("外部類的成員方法");
}
public void test() {
System.out.println("外部類的test方法");
}
//爲了將內部類的語句輸出
//在【外部類】中定義【內部類的類對象】
public void createInnnerClass() {
//外部類的成員方法中調用內部類的構造方法,通過new關鍵字來創建內部類類對象使用
Inner inner = new Inner();
inner.testInner();
}
public static void testStatic() {
System.out.println("外部類的靜態成員方法~");
}
}
//☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
public class Demo1 {
public static void main(String[] args) {
//創建內部類對象的方法一:在外部類中創建內部類對象
// Outer outer = new Outer();
// outer.createInnnerClass();
//創建內部類對象的方法一:在類外創建一個Inner類對象
//數據類型是Outer.Inner 表示是外部類裏面的內部類數據類型
Outer.Inner inner = new Outer().new Inner();
inner.testInner();
}
}
運行結果:
內部類的成員方法
外部類的成員方法
內部類成員變量:50
外部類成員變量:100
內部類的test方法
外部類的test方法
10
外部類的靜態成員方法~
(二)局部內部類
放在方法內或者函數內的類,稱之爲局部內部類
-
【第二個知識點】
- 1.局部變量的
- 【生存期】
從聲明定義位置開始,到代碼塊結束 - 【作用域】
只能在當前代碼塊內部
- 【生存期】
- 2.類對象的
- 【生存期】
通過new關鍵字創建的時候開始,JVM垃圾回收機制調用時,銷燬該對象,結束 - 【作用域】
那個引用變量擁有這個對象的首地址,哪裏就是他的作用域
- 【生存期】
- 1.局部變量的
-
說明:
局部內部類只能在函數或者方法中使用,方法或者函數外不能使用 -
發現:
局部內部類中,貌似不能【修改】所在方法或者函數中的局部變量,原因如下:
局部內部類的對象是在函數或者說方法的內部通過JVM藉助於new關鍵字,和局部內部類的構造方法,創建的一個類對象,並且該對象是由JVM的垃圾回收機制回收的;
但是局部變量n是在testInner()方法中,而這個局部變量的n的生存週期是和testInner()該方法的大括號有關,生存期和作用域都是在大括號以內;
如果在testInner()方法的內部,MethodInner()這個類是方法中的局部內部類,而創建的對象在使用testInner()方法中的局部變量時,因爲對象的銷燬時間不確定,但是一定是晚於局部變量的銷燬的,這裏隱含了一個類對象【延長了局部變量生存期】,這個是不符合Java原理的;
這就是爲什麼不能修改,因爲有可能在這個對象被使用的時候,局部變量的內存空間已經被內存收回,換而言之這個局部變量不存在了。
- 【解決方法】
如果是在局部內部類中使用的所在函數或者方法的局部變量,該變量用final修飾
package com.qfedu.a_innnerclass;
class Test {
int num = 100;
public void testInner() {
//這裏是在方法中定義了一個類,這個類只能在當前函數或者方法中使用
final int n = 10;
class MethodInner {
int i = 10;
public void function() {
System.out.println(i);
num = 20;
System.out.println(num);
//n = 20;//使用final之後就可以了
System.out.println(n);
System.out.println("局部內部類的成員方法");
}
}
MethodInner methodInner = new MethodInner();
methodInner.function();
}
}
public class Demo2 {
public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// System.out.println(i);
// }
// System.out.println(i);
new Test().testInner();
}
}
程序運行結果:
10
20
10
局部內部類的成員方法
(三)匿名內部類
只是沒有了名字,但是類本體中該實現的還得實現;
且匿名內部類必須繼承一個類或者實現一個接口;
【第四個知識點】
類的本體:在類聲明部分,大括號裏面的內容就是類 的本體!!!
class Test {
//成員變量
//成員方法
}
匿名內部類就是沒有名字的類,但是有類的本體!!!
package com.qfedu.a_innnerclass;
import java.util.Arrays;
import java.util.Comparator;
abstract class Animal {
int age;
public abstract void test();
abstract public void jump();
}
class CodingMonkey extends Animal {
//以下兩個方法是類的本體!!!
@Override
public void test() {
System.out.println("這是類本體中的一個成員方法");
}
@Override
public void jump() {
System.out.println("Hello");
}
}
interface A {
int i = 0; // public static final
public void testA(); //abstract
}
public class Demo3 {
public static void main(String[] args) {
//方法一:正常方法: 可以創建CodingMonkey即Animal的子類的對象,進行方法的操作
CodingMonkey cm = new CodingMonkey();
//Animal a = cm; 多態,父類的引用指向子類的對象~
cm.test();
cm.jump();
//方法二:匿名對象類: 也可以直接通過匿名對象類進行直接操作
/*
Animal是一個抽象類,ani 是抽象類的一個引用類型變量
new Animal() {
發現這裏面的內容和繼承該抽象類的子類內容一致,都是抽象類,要求子類實現的方法;同時這裏是類的本體,而這裏可以看做是一個類,但是這個類沒有名字,所以:這個就是【匿名內部類】;本質上這裏創建了一個【匿名內部類】的對象,賦值給了Animal的引用數據類型 ani,同時這裏隱含了一個【繼承】關係
};
*/
Animal ani = new Animal() {
//同樣需要實現繼承的相關功能
@Override
public void test() {
System.out.println("匿名內部類的裏面的test方法");
}
@Override
public void jump() {
System.out.println("匿名內部類的Hello");
}
};
ani.jump();
ani.test();
//以上更加簡潔的使用方式:
//匿名內部類的匿名對象直接調用方法
new A() {
//這裏是一個類【遵從】接口之後,要求實現接口中的抽象方法,這裏也是一個【匿名內部類】
@Override
public void testA() {
System.out.println("匿名內部類實現interface A中的testA方法");
}
}.testA();//new A(){}可以看成匿名對象
//Java Android開發
Integer[] arr = {3, 2, 4, 5, 6, 1};
//匿名內部類的匿名對象作爲方法的參數
Arrays.sort(arr, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(Arrays.toString(arr));
}
}
程序運行結果:
這是類本體中的一個成員方法
Hello
匿名內部類的Hello
匿名內部類的裏面的test方法
匿名內部類實現interface A中的testA方法
[1, 2, 3, 4, 5, 6]
- 在使用匿名內部類的過程中,我們需要注意如下幾點:
1、使用匿名內部類時,我們必須是繼承一個類或者實現一個接口,但是兩者不可兼得,同時也只能繼承一個類或者實現一個接口。
2、匿名內部類中是不能定義構造函數的。
3、匿名內部類中不能存在任何的靜態成員變量和靜態方法。
4、匿名內部類爲局部內部類,所以局部內部類的所有限制同樣對匿名內部類生效。
5、匿名內部類不能是抽象的,它必須要實現繼承的類或者實現的接口的所有抽象方法。
三、多線程與多進程
-
多線程和多進程:
- 線程:
在一個軟件中,負責不同功能的子程序,稱之爲線程。 - 進程:
是在計算機系統中所有正在運行的程序,都可以看做是一個進程。多進程的操作系統
- 線程:
-
從實際出發:
每一個CPU的內核在一個時間片內只能執行一件事情。但是這個時間片非常的短,不同的程序會出現在不同的時間片上,不斷的切換,所以你感覺是在同時運行,現在的CPU基本上都是多核多線程的。 -
面試題:
請問一個Java程序再運行的時候,最少有幾個線程???
最少是兩個線程:1. main線程,即主線程 2. JVM的垃圾回收機制 -
多線程的好處:
1.可以讓一個程序同時執行不同的任務
2.可以提高資源的利用率 -
多線程的弊端:
1.線程安全問題 之前的迭代器
2.增加了CPU負擔
3.降低了其他程序CPU的執行概率
4.容易出現死鎖行爲 -
如何創建一個線程:
兩種方式:
1.自定義一個類繼承Thread類,那麼這個類就可以看做是一個多線程類
2.要求【重寫】Thread類裏面的run方法,把需要執行的自定義線程代碼放入這個run方法
下面是使用迭代器出現的同時操作問題;
package com.qfedu.b_thread;
import java.util.ArrayList;
import java.util.Iterator;
class MyThread extends Thread {
//要求重寫【run】方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("自定義Thread類:" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
//1. 創建自定義線程類的類對象
MyThread mt = new MyThread();
//2. 啓動線程 。
//mt.run();嘗試調用n方法 相當於調用了一個普通的方法,而不是一個線程
mt.start(); //啓動線程的方式,會發現,MyThread線程和main線程在搶佔CPU資源
for (int i = 0; i < 10; i++) {
System.out.println("main線程:" + i);
}
/*
ArrayList<String> list = new ArrayList<String>();
list.add("hello, how do you do");
list.add("I'm fine, Thank you , and you");
list.add("I'm fine, too");
//三種遍歷方式:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for (String string : list) {
System.out.println(string);
}
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
*/
}
}