作为一只菜鸟,记录自己学习Java的过程,欢迎大家一起交流进步!本文是自己整理的一些java基础部分的知识,还有一些模块待添加。。。 第一章 基础知识
硬盘–>内存–>cpu px:像素
url:统一标识符 http:超文本传输协议
软件架构:BS架构 browser serve 通过浏览器 CS架构 client serve 通过客户端
GUI: 图形化界面
作为程序员要求掌握最基本的windows相关DOS命令:
快捷键:win+R(开始-运行-输入cmd-回车)
常见命令:(键盘的上下键可以调出之前写的命令行)
1、dir 列出当前目录下所有子目录/子文件
2、cd change directory改变目录
用法:当前在C:UsersJuly,此时输入cd Desktop为相对路径,或者此时输入cd C:UsersJulyDesktop则为绝对 路径 cd… 返回上一级目录(逐级) cd/ 返回到最外面的目录(一步到位)
3、md name 新建名字为name的文件夹 (补充:echo name:Tom.age=12>1.doc 在文件夹中创建一个名字是1的doc格式并写入name:Tom.age=12
4.del 删除文件 del *.txt 删除txt文件 rd 删除文件夹(需要保证文件目录里面为空)
exit 退出 cls 清屏
cd ..返回上一级目录 cd 直接回到根路径
切换盘符:c + : + 回车,如c: 回车、d: 回车…(不需要cd)
要做到跨平台通过JAVA虚拟机——JVM
.java文件编译成为.class文件
在DOS窗口直接输入ipconfig可以查看当前的ip地址
public class 和 class的区别:
一个java源文件中可以定义多个class一个java源文件当中public class不是必须的一个class会定义生成一个xxx.class字节码的文件一个java源文件当中定义公开的类的话,public的class只能有一个,并且该类的名称必须和java源文件名称一致
(public class M 则源文件的名称必须是M.java)
面向对象的三大特征
*封装 (Encapsulation)
*继承 (Inheritance)
*多态 (Polymorphism)
JDK 开发工具包 JRE 运行环境
javac.exe:进行编译 .java源文件–>.class字节码文件(是类名)
java.exe:运行 .class–>结果
Java中的注释:
1、-单行注释: //
2、-多行注释:
//public表示公开的
//class 表示定义一个类
//HelloWorld 表示一个类名
public class HelloWorld{ //表示定义的一个公开的类,起名HelloWorld
public static void main (String[] args){//表示定义一个公开的静态主方法
//java语句以分号结尾
//字符串用双引号
System.out.println("Hello,World");//打印一行line 会换行,print不会换行
}
}
3、文档注释(java特有):
API:应用程序接口
IDEA快捷键1、创建构造器、重写函数快捷键:Alt + Insert
2、整理代码:Ctrl + Alt + L
3、基本快捷输入:
main 或者 psvm ----public static void main(String args[])
sout ---- System.out.println()
4、equals
if(p.getStatus().getNAME().equals("BUSY")){
throw new TeamException("该员工已是某团队成员");
}
//这种表示会更好 因为BUSY是确定值 而p.getStatus().getNAME()可能会出现空指针的情况
if("BUSY".equals(p.getStatus().getNAME())){
throw new TeamException("该员工已是某团队成员");
}
5、instanof
多使用,判断a是不是b的子类,很巧妙
6、toString
System.out.println ---- 自动调用toString方法 重写了的话就调用重写的方法
7、补齐左边代码
ctrl + alt + v
8、抛出异常
alt + enter
9、添加方法注释
a + tab (前面的a可以修改)
或系统自带: public class Utility { private static Scanner scanner = new Scanner(System.in); public static char readMenuSelection() { char c; for (; ; ) { String str = readKeyBoard(1); c = str.charAt(0); if (c != '1' && c != '2' && c != '3' && c != '4') { System.out.print("选择错误,请重新输入:"); } else break; } return c; } public static int readNumber() { int n; for (; ; ) { String str = readKeyBoard(4); try { n = Integer.parseInt(str); break; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:"); } } return n; } public static String readString() { String str = readKeyBoard(8); return str; } public static char read/confirm/iSelection() { char c; for (; ; ) { String str = readKeyBoard(1).toUpperCase(); c = str.charAt(0); if (c == 'Y' || c == 'N') { break; } else { System.out.print("选择错误,请重新输入:"); } } return c; } private static String readKeyBoard(int limit) { String line = ""; while (scanner.hasNext()) { line = scanner.nextLine(); if (line.length() < 1 || line.length() > limit) { System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:"); continue; } break; } return line; } }
class FamilyAccount{
public static void main(String[] args){
//初始化
boolean isFlag = true;//条件
int balance = 10000;//余额
String record = "收支t账户余额t收支金额t说明n";//存储记录,通过字符串连接进行存储记录
lable:while(isFlag){
//创建对话界面
System.out.println("----------------家庭收支记账记录----------------");
System.out.println(" 1 收支明细");
System.out.println(" 2 登记收入");
System.out.println(" 3 登记支出");
System.out.println(" 4 退 出n");
System.out.print(" 请选择(1-4):");
//读取键盘的输入(字符)
char choice = Utility.readMenuSelection();
//对键盘的输入数字进行分类执行
switch(choice){
case '1':
System.out.println("----------------当前收支明细记录----------------");
System.out.println(record);
//System.out.println("收支明细");
break;
case '2':
//读取键盘输入的金额数字
System.out.print("本次收入金额:");
int income = Utility.readNumber();
balance += income;//调整余额
//读取键盘输入的说明
System.out.print("本次收入说明:");
String inExplanation = Utility.readString();
//存储记录
record += ("收入t" + balance + "tt" + income + "tt" + inExplanation + "n");
//System.out.println("登记收入");
System.out.println("----------------登记完成----------------");
break;
case '3':
//读取键盘输入的金额数字
System.out.print("本次支出金额:");
int expenses = Utility.readNumber();
if(expenses <= balance){
balance -= expenses;//调整余额
}else{
System.out.println("支出大于余额,入不敷出!");
continue lable;//跳转到循环的开始
}
//读取键盘输入的说明
System.out.print("本次支出说明:");
String outExplanation = Utility.readString();
//存储记录
record += ("支出t" + balance + "tt" + expenses + "tt" + outExplanation + "n");
//System.out.println("登记支出");
System.out.println("----------------登记完成----------------");
break;
case '4':
System.out.print("确认是否退出(Y/N):");
char isExit = Utility.read/confirm/iSelection();
if(isExit == 'Y'){
isFlag = false;
}else{
continue lable;//跳转到循环的开始
}
//System.out.println("退 出");
break;
}
}
}
}
Eclipse:
ctrl + Alt +g 查看该函数使用的位置
第三章 数组 3.1 一维数组//初始化完成后长度就确定了
//静态初始化
int[] num = new int[]{1,2,3,4};
//动态初始化
String[] names = new String[5];
//索引 0开始-1结束
name[0] = "123";
//获取长度 length
int lo = name.length;
//遍历 for循环
内存的结构:
1、栈(stack):存放局部变量,指向堆中数据的首地址
2、堆(heap):new的对象,数组
3、方法区:常量池(String)、静态域(static)
import java.util.Scanner;
//将键盘输入的n个成绩存入列表里面
public class StudentScore {
public static void main(String[] args) {
//获取学生人数,确定数组的长度
System.out.print("请输入学生人数:");
Scanner scan = new Scanner(System.in);
int studentNum = scan.nextInt();
//System.out.println(studentNum);
//存储输入的成绩,并判断出最高分
System.out.println("请输入"+ studentNum + "个成绩:");
int[] scores = new int[studentNum];
int maxScores = 0;
for(int i = 0;i < scores.length;i++) {
scores[i] = scan.nextInt(); //循环赋值 关键
if(scores[i] > maxScores) {
maxScores = scores[i];
}
}
System.out.println("最高分是:" + maxScores);
//判断学生成绩等级
char level;
for(int j = 0;j < scores.length;j++) {
if(maxScores - scores[j] <= 10) {
level = 'A';
}else if(maxScores - scores[j] <= 20) {
level = 'B';
}else if(maxScores - scores[j] <= 30) {
level = 'C';
}else {
level = 'D';
}
System.out.println("student " + j + " score is " + scores[j] + " grades is " + level);
}
}
}
3.2 二维数组
//相当于一维数组里面存放一维数组
//静态初始化
int[][] array = new int[][]{{1,2},{3,4,5}.{5,4,5}};
//动态初始化1
String[][] array2 = new String[3][2];
//动态初始化2
String[][] array2 = new String[3][];
//索引
array[0][1];
//获取长度
array.length // =3
array[0].length // =2
//遍历 嵌套for循环
for(int i = 0;i < array.length;i++){
for(int j = 0;j < array[i].length;j++){
}
}
//默认初始化值
array2[0] //输出的是首地址值
array2[0][0] // 0
int[] arr 等价于 int... arr //但是有确定的个数的方法优先执行,比如有三个参数传入那么三个参数的重载方法优先于这个执行
二维数组的内存解析:左栈、右堆,栈指向堆中的首地址值
※重点:引用类型存储(传递的)的要么是地址值要么是null
3.3 实例 3.3.1 数组算法1、杨辉三角:
//杨辉三角
public class YangHuiTest {
public static void main(String[] args) {
//初始化二维数组
int[][] yangHui = new int[10][];
for(int i = 0;i < yangHui.length;i++) {
//确定每一行的长度
yangHui[i] = new int[i+1];
//将1赋值给第一个和最后一个
yangHui[i][0] = 1;
yangHui[i][i] = 1;
//中间的数进行赋值
for(int j = 1;j < yangHui[i].length-1; j++) {
yangHui[i][j] = yangHui[i-1][j-1] + yangHui[i-1][j];
}
}
//输出二维数组
for(int m = 0;m < yangHui.length;m++) {
for(int n = 0;n < yangHui[m].length;n++) {
System.out.print(yangHui[m][n] + "t");
}
System.out.println();
}
}
}
2、回形数(算法题,后续自己加)
3、算一个数组中的最大、最小、平均值、总和等(可以取数组第一个数为初始值)
//两位数的随机数 (int)(Math.random()*(99-10+1)+10);3.3.2 数组赋值
//是将一个数组的地址赋值给另一个地址,两个同时变
//new 了多少个数组才会有多少个数组
int[] array1,array2;
array1 = new int[]{1,2,3,4};
array2 = array1 //不是复制,相当于创建了快捷方式
3.3.3 数组复制、反转、查找
//复制
//需要new一个,然后for循环一一对应
array2 = new int[array1.length];
for(int i = 0;i < array1.length;i++){
array2[i] = array1[i];
}
//反转
//加一个临时变量就可以了 temp
//查找
//线性查找
String dest = "AA";
dest.equals(...); //相等,类似int形式的 ==是比较地址,而equal是比较值是否相等
//二分法查找(折半查找) 前提是数组有序
int[] array = new int[]{-12,-10,-9,-5,1,3,7,9,20,234,567,789};
int dest = 789;
int head = 0; //初始首索引
int end = array.length - 1; //初始尾索引
boolean isFlag = true;//用于判断是循环break结束了还是循环条件结束的
while(head <= end){
int mid = (head + end)/2; //初始中间索引 关键
if(dest == array[mid]){
System.out.println("找到了,位置是:" + mid);
isFlag = false;
break;
}else if(dest < array[mid]){
end = mid -1; //记得 -1 关键
}else if(dest > array[mid]){
head = mid + 1; //记得 +1 关键
}
}
if(isFlag) {
System.out.println("没有找到");
}
3.3.4 数组排序
//冒泡排序 BubbleSort 相邻的两个数进行比较 时间复杂度O(n^2)
int[] array = new int[] {-11,20,44,10000,1,2,3,4,-12,45,-23,100,46,-128,1289};
//总共有array.length - 1轮排序
for(int i = 0;i < array.length - 1;i++) {
//每一轮有array.length - 1 - i次排序
for(int j = 0;j < array.length - 1 - i;j++) { //关键
//相邻的两个数进行排序,交换位置 关键
if(array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
for(int m = 0;m
3.4 Arrays工具类
import java.util.Array;
3.5 数组异常
针对具体的问题进行分析
第四章 面向对象(上)
Car carKey; // 是对象的引用
new Car(); //是对象
Car carKey = new Car(); //将对象的引用和对象关联起来 即引用指向对象
面向对象三大特征:封装、继承、多态
java类及类的成员: 类是名词, 类中包含着属性、方法、构造器;代码块、内部类
面向过程:强调功能行为,以函数为最小单位,考虑怎么做(打开冰箱、放入、关门)调用函数即可
面向对象:强调具备功能的对象,以类/对象为最小单位,考虑谁来做(人(打开冰箱、关闭冰箱)、冰箱(打开门、关闭门)、大象)创建对象
面向对象的两个要素:
类(class):对一类事物的描述,是抽象的、概念上的定义
对象(object)实际存在的事物的每个个体,称为实例(instance)
4.1 类的成员
4.1.1 属性和局部变量
属性:成员变量 、 field,字段
方法:成员方法、函数、method
属性和局部变量的区别:
相同点:
1、声明的格式一样
2、都有作用域
不同点:
属性:1、直接定义在类的一对{ }内,2、可以使用权限修饰符(private、public、protected、缺省),3、有默认初始化值 ,4、堆空间里面
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量、没有默认初始化值需要先初始化,栈空间里面
class Person{
//属性
String name;
private int age; //加修饰符
public void talk(){
//局部变量
String language = "Chinese"; //需要先初始化
}
}
对属性可以赋值的位置:
①默认初始化 没有赋值时会有默认值
②显式初始化 直接在定义时进行赋值
③构造器中初始化
④创建对象以后,可以通过“对象.属性”或“对象.方法”的方式进行赋值
⑤在代码块中赋值
先后顺序:① --②/⑤ --③–④
4.1.2 方法
类应该具有的功能
//权限修饰符 返回值类型 方法名(形参列表)
public String talk(String word){
String info = "说了" + word; //方法体
return info;
}
return; //可以作为结束方法的地方
方法中可以调用类的属性和方法
万事万物皆对象:
1、java中将功能、结构等封装到类中,通过类的实例化来调用具体的功能结构
2、与前端html、后端的数据库交互时,在java层面交互时都体现为类和对象
1、方法的重载(overload)
重载:在同一个类中,允许存在一个以上的同名方法。只要它们的参数个数或者参数类型不同即可
——与方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系,只看参数列表就可以了
//以下就是方法的重载
public void getSum(int a,int b){
}
public void getSum(double a,double b){
}
public void getSum(int a,int b,int c){
}
//可变个数形参的格式:数据类型 ... 变量名 等价于 数组类型[] 数组名
public void getSum(double a,int ... num){ ==(double a ,int[] b)
}// double a和int ... num不可以互换位置,可变的只能放在后面
2、方法参数的值传递机制
关于变量的赋值:
①基本数据类型:直接将数据值进行赋值
②引用数据类型:将数据的地址值进行赋值,数组没有new只是赋值的话就会指向同一个地址的数组,一个改变则同时改变
方法的形参传递机制——值传递:
1、形参:方法定义时,声明在小括号内的参数
实参:方法调用时,实际传递给形参的数据
2、值传递机制(一个改变另外一个不会改变,传递的是地址值才会同时改变)
①如果参数是基本数据类型(包括取数组中的某个值的时候),此时实参传递给形参的是实参的数据值
②如果参数是引用数据类型(数组、类、接口),此时实参赋给形参的是实参存储数据的地址值(引用类型传递的不是null就是地址值)
//冒泡排序
public void sort(int[] arr){
for(int i = 0;i < arr.length -1;i++){
for(int j =0;j < arr.length - 1 -i;j++){
if(arr[j] > arr[j+1]){
swap(arr,j,j+1); //方法调方法
}
}
}
}
//传入引用类型,才可以达到地址传递的目的
public void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
方法结束以后在方法内部定义的变量都会被弹出栈(包括赋值了地址值的引用变量)后进先出
内存结构:栈(局部变量)、堆(new 出来的结构:对象(非static成员变量)、数组)
递归:从后往前进行计算
if --else 结构
//从1加到n的和
public int getSum(int n){
if(n == 1){
return 1; //递归的终点
}else{
return n + getSum(n - 1);
}
}
4.1.3 构造器(构造方法)
构造器(constructor)的作用:
①创建对象:new 后面的就是构造器
②初始化对象的信息
说明:1、如果没有显式的定义类的构造器的话,则系统默认提供一个空参的构造器
2、定义构造器的格式:权限修饰符 类名(形参列表){ } 可以重载
3、一旦显式的定义构造器后,系统就不再提供默认的空参构造器
4、默认构造器的权限和类的权限相同
public class PersonTest{
public static void main(String[] args){
Person p1 = new Person();//Person()就是构造器
Person p2 = new Person(12);//Person(12)就是构造器
}
}
class Person{
//属性
private int age;
//构造器 (无参构造器)
public Person(){
System.out.println(".......");
}
//有参构造器,可以在创建对象的时候初始化属性
public Person(int a){
age = a;
}
//方法
public void eat(){
System.out.println("吃饭");
}
}
总结:属性的赋值方法(先后顺序) ①默认初始化——②显式初始化——③构造器中初始化——④通过“对象.方法”或“对象.属性”的方式赋值
拓展知识:JavaBean
JavaBean : 一种java语言写成的可重用组件
1、类是公共的
2、有一个无参的公共构造器
3、有属性,且有对应的get、set方法
public class Person{
//属性
private int age;
private String name;
//空参构造器
public Person(){
}
//get、set方法
public void setAge(int a){
age = a;
}
public int getAge(){
return age;
}
public void setName(String n){
name = n;
}
public String getNmae(){
return name;
}
}
拓展知识:UML类图
4.2 关键字
4.2.1 this关键字
this:表示当前对象 或 当前正在创建的对象(构造器中)
可以调用类的属性、方法和构造器只能调一个(this(形参列表);) (形参和属性同名时使用this) 大部分时候是省略的
public class Person{
//属性
private int age;
private String name;
//空参构造器
public Person(){
}
//有参构造器
public Person(int age){
this(); //调用空参构造器,必须在首行
this.age = age;
}
//有参构造器
public Person(String name,int age){
this(age); //调用有参构造器 ,必须在首行
this.name = name;
}
//get、set方法
public void setAge(int age){
this.age = age; //形参和属性同名时使用this
}
public int getAge(){
return age;
}
public void setName(String name){
this.name = name;
}
public String getNmae(){
return name;
}
}
4.2.2 package关键字
1、为了更好的实现类的管理
2、使用package声明类或者接口所属的包,声明在源文件的首行
3、包,属于标识符,都是小写,每 . 一次就是一层文件目录 com.test. 。。。
4.2.3 import关键字
1、在源文件中显式使用import结构导入指定包下的类、接口 import Java.util.Arrays
2、声明在包的声明和类的声明之间
3、可以使用 import Java.util.*;的方式,导入包中的所有结构
4、Java.lang包可以省略
5、如果在源文件中,使用了不同包下的同名的类,则至少有一个类需要以全类名的方式显式(包含包的路径)
项目二 客户信息管理
第五章 面向对象(中)
5.1面向对象的特征
5.1.1 封装与隐藏
属性的封装性——将属性的赋值和修改封装成public方法来进行,同时将属性用private进行修饰避免外部直接修改属性
权限修饰符(4种权限从小到大):private 、缺省、protected、public
修饰类和类的内部结构:属性、方法、构造器、内部类
5.1.2 继承(Extends)
①减少代码的冗余,增加代码的复用性
②便于功能的扩展、为多态的使用做准备
格式:class A extends B{}
A:子类、派生类、subclass
B:父类、超类、superclass
继承以后,子类A就获取了父类B声明的结构:所有的属性和方法(包括private的属性和方法,只不过因为封装性不可以直接调用)
1、一个类可以被多个子类继承
2、Java中的单继承性:一个类只可以有一个父类
3、子类直接继承的父类称为直接父类,间接继承的父类称为间接父类
4、子类继承父类以后,就获得了直接父类以及所有间接父类中的声明的属性和方法
**Object类:**所有的java类都直接或间接的继承于java.lang.Object类 (根节点)
5.1.3 多态(Polymorphism)
——只调用重写的方法!!!!!!!!!!!!!!!!!!!
1、理解:一个事物的多种形态,是运行时行为(不是编译时行为,只有运行时才知道是哪个)
2、对象的多态性:父类的引用引向子类的对象
3、多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法——虚拟方法调用
编译看左(父类),运行看右(子类)编译的时候看的是父类中有没有这个方法,
但是运行的时候是运行子类中的重写的方法
//对象的多态性:父类的引用指向子类的对象
Person p = new Man();
//感受多态性的好处
class PersonTest{
public static void main(String[] args){
PersonTest test = new PersonTest();
test.func(new man()); //相当于Person person = new Man();
//new谁就可以调用谁,可以简化代码,减少重载的方法func,否则针每一个对象都需要有一个对应的重载方法func
}
void func(Person person){
person.eat();
}
}
class Person{//父类有这个方法
void eat(){
System.out.println("吃东西");
}
}
class Man extends Person{//子类重写这个方法
void eat(){
System.out.println("多吃东西");
}
}
class Women extends Person{
void eat(){
System.out.println("少吃东西");
}
}
4、对象的多态性只适用于方法,不适用于属性(编译和运行都只看左边)
向上转型(多态):
——子类–>父类
向下转型:
—— ,使用强制类型转换 父类–>子类
Person p = new Man();
Man m1 = (Man)p;
多态:!!!!!!!! 不能调用子类中特有的方法!!!!!!!!!!!
只可以调用子类中重写父类的方法(也就是父类中有的方法,只不过执行的时候是执行子类的)
原因:有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的(因为new过了),但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用
instanceof关键字:
为了避免在向下转型时出现异常(ClassCastException),在向下转型前用instanceof进行判断
如果a instanceof A为true,a instanceof B也是true,那么B是A的父类
a instanceof A
//判断对象a是否是类A的实例
//如果是返回true,如果不是返回false
//对象的多态性:父类的引用指向子类的对象
Person p = new Man();
if(p instanceof Man){//则可以执行
Man m1 = (Man)p;
}
if(p instanceof Person){//也可以执行
}
理解多态:
1、若子类重写了父类中的方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里面的方法转移到子类中,编译看左边,运行看右边
2、对于实例变量则不存在这样的情况,即使子类里面定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量,编译运行都看左边
5.2 方法的重写(override)
重写:在子类中可以根据需要对从父类中继承的同名同参数方法进行覆盖操作,方法的重置、覆盖(子类的方法将覆盖父类的方法),晚绑定,动态绑定,表现为多态性
重载:在同一个类中,允许存在一个以上的同名方法(构造器也可以)。只要它们的参数个数或者参数类型不同即可,早绑定,静态绑定,不表现为多态性
细节:
1、子类重写的方法名和形参列表和父类的相同
2、子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
特别的:子类不可以重写父类中private修饰的方法
3、返回值类型:
①void 子类和父类相同
②A类 子类可以是A类或者A类的子类
③基本数据类型 子类相同的基本数据类型
4、子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常
static :子父类同名同参数方法要么都为非static(考虑重写),要么都声明为static(不是重写,无法覆盖)
5.3 四种权限修饰符
权限修饰符(4种权限从小到大):private 、缺省、protected、public
修饰类和类的内部结构:属性、方法、构造器、内部类
protect:不同包的子类,不是子类的话还是用不了
5.4 super关键字
1、super理解为:父类的
2、super调用:属性、方法 ( super.属性或super.方法)
3、super调用:构造器 在子类的构造器中使用:super(形参列表); 用在首行(类似this(形参列表);)
4、子类构造器中没有显式super();,但是其实默认调用父类的空参构造器,构造器中至少有一个是有super,其余可以是this
5.5 子类对象实例化的过程
1、从结果上看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法
创建子类对象,在堆空间中就会加载所有父类的声明(直接父类和间接父类)
2、从过程上看:
当通过子类的构造器创建子类对象时,一定会直接或者间接的调用父类的构造器,
进而调用父类的父类的构造器,直到java.lang.Object
(因为一个类的构造器中至少有一个是有super,其余可以是this)
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象
final:关键字最终的意思,定义属性表示不能改变
fianlly:捕捉异常要用,关闭资源
finalize:垃圾回收机制调用的垃圾回收方法
5.6 Object类:
1、Object类是所有Java类的父类
2、属性:无
方法:equals() toString()…
一、equals() :
== 和 equals() 区别:(面试题)
==运算符:
1、可以使用在基本数据类型变量和引用数据变量中
2、基本数据类型变量:比较两个变量保存的数据是否相等(不一定要类型相同)
引用数据类型变量:比较两个变量的地址值是否相等,两个引用是否指向同一个对象实体
equals()方法:
1、是一个方法,不是运算符
2、只能适用于引用数据类型 a.equals(b);
3、Object中定义的 equals() 和 == 是相同的
public boolean equals(Object anObject){
return (this == anObject); //this表示调用这个方法的对象 a.equals(b);
}
4、String 、Date、File、包装类等都重写了Object中的 equals()方法,重写以后比较的不是两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同
5、通常情况下我们自定义的类使用equals()的话,也是比较两个对象的“实体内容(属性等)”是否相同
//手动实现 或者也可以自动生成source里面
public boolean equals(Object obj){
//比较两个地址是否相同,地址相同就肯定是相等的
if(this == obj){
return true;
}
if(obj instanceof Customer){
//这里强制转(向下转型)是为了可以访问子类中的属性
Customer cust = (Customer)obj;
//比较两个对象的属性是否相同
if(this.age == obj.age && this.name.equals(cust.name)){
//基本数据类型用==比较,引用数据类型用equals()进行比较(这里的equals()方法是String的,因为使用者的类型是String(体现了多态))
return true;
}else{
return false;
}
}
}
二、toString()方法
1、当我们输出一个对象的引用(引用类型)时,实际上就是调用当前对象的toStrting()方法
2、String 、Date、File、包装类等都重写了Object中的toString()方法,使得可以直接返回“实体内容”信息
3、自定义类也可以重写toString()方法(可以手动实现或者自动生成source中)
5.7 包装类(Wrapper)
——用于基本数据类型的面向对象(具有类的特征)
单元测试方法:JUnit单元测试
步骤:
1、选中当前工程–右键选择:build path - add libraries - Junit 4 - 下一步
2、创建java类,进行单元测试
此时的java类要求:①此类是public的 ②此类提供公共的无参的构造器(默认就有)
3、此类中声明单元测试方法:
此时的单元测试方法:方法的权限是public,没有返回值,没有形参
4、此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
5、声明好单元测试方法以后,就可以在方法内测试相关的代码
6、写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
说明:
1、如果执行结果没有任何异常:绿条
2、如果执行结果有异常:红条
public class JUnitTest {
int num = 10;
@Test
public void nameTest() {//就看成可以先调试的普通空参方法
System.out.println("name");
System.out.println(num);//可以调用属性,不用创建对象
}
}
包装类的使用:
只有转换成包装类才可以是Object的子类
新特性:自动装箱和拆箱
//自动装箱 基本数据类型-->包装类
int num = 10;
Integer m = num;
//自动拆箱 包装类-->基本数据类型
int num1 = m;
//因而可以将包装类和基本数据类型归到一起
//包装类和基本数据类型-->String类
String stm = String.valueOf(num);
String stm1 = String.toString(num);
//String类-->包装类和基本数据类型
int mun = Integer.parseInt(stm);
三元运算符会将两个表达式进行自动类型提升成相同的类型
//利用Vector代替数组处理:从键盘读取学生成绩(负数代表输入结束), 找出最高分并输入学生成绩等级
public class studentGrades {
public static void main(String[] args) {
//1、键盘输入
Scanner scan = new Scanner(System.in);
//2、创建Vector对象,用于存储成绩
Vector v = new Vector();
//3、将键盘输入存入v中
int max = 0;
for(;;) {
System.out.println("请输入学生成绩:");
int grades = scan.nextInt();
if(grades < 0) {
break; //跳出循环 关键
}
if(grades > 100) {
System.out.println("输入错误请重新输入");
continue; //进行下一轮循环 关键
}
//自动装箱 关键
v.addElement(grades);
//4、获取最大值
if(max < grades) {
max = grades;
}
}
//5、输出成绩等级
char level;
for(int i = 0;i < v.size();i++) {
//先向下转型,再自动拆箱 关键
int scores =(Integer)v.elementAt(i);
if(max - scores <= 10) {
level = 'A';
}else if(max - scores <= 20) {
level = 'B';
}else if(max - scores <= 30) {
level = 'C';
}else {
level = 'D';
}
System.out.println("学生" + i + "的成绩等级是" + level);
}
}
}
第六章 面向对象(下)
6.1 关键字 static
static 关键字的使用:
1、static 静态的
2、修饰:属性、方法、代码块、内部类
3、使用static修饰属性:静态变量(属性)、类变量
3.1 属性,按是否使用static修饰,又分为:静态属性 VS 非静态属性(实例变量)
实例变量:创建类的多个对象,每个对象之间的非静态属性互相不会产生关联,是独立的
静态属性:创建类的多个对象,多个对象共享同一个静态变量,修改一个则所有都变(一发而动全身)
3.2 static修饰属性的其他说明:
①静态变量随着类的加载而加载,可以通过“类名.静态变量”的方式进行调用
②静态变量的加载要早于 对象的创建
③由于类只会加载一次,则静态变量在内存中也会只存在一份,存在方法区的静态域中
④ 类对象 实例变量
类 yes no(类名不可以直接调用非静态变量,需要实例化后,用对象进行调用)
对象 yes yes
3.3 举例:System.out.println、Math.PI
4、使用static修饰方法:静态方法
①随着类的加载而加载的,可以通过“类名.静态方法”的方式进行调用
② 静态方法 非静态方法
类 yes no(类名不可以直接调用非静态方法,需要实例化后,用对象进行调用)
对象 yes yes
③静态方法中,只能调用静态的方法或属性——主要原因是静态和非静态的生命周期不同,晚出生的可以调用早出生的,反之不行
(因为静态方法是随着类的加载就加载的,是先于创建对象的,如果静态方法里面有非静态的方法或者属性的话,那此时在创建对象之前用“类名.静态方法”调用这个静态方法的话,那这个方法里面的非静态部分还没有产生,会出现矛盾,所以非静态的属性和方法是不可以在静态方法里面使用的)
非静态方法中,非静态和静态的方法或者属性都可以使用
5、static注意点:
在静态方法内,不能使用this、super关键字(因为都是基于传入的对象的,此时对象还没有创建)
6、开发中,属性是否static
· 属性是可以被多个对象所共享的,不会随着对象的不同而变化的
· 类中的常量一般也是static (static final)
7、开发中,方法是否static
· 操作静态属性的方法,通常是静态的,比如静态属性对应的get、set方法
· 工具类中的方法,习惯上是静态的,比如Math、Arrays
设计模式
——23种设计模式
——在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。(套路)《大话设计模式》
单例设计模式(Singleton ):只生成一个实例,减少系统性能开销 如 java.util.Runtime
实现方式:饿汉式 vs 懒汉式
public class Singleton{
public static void main(String[] args){
Bank bank1 = Bank.getBank();
Bank bank2 = Bank.getBank();//两个指向同一个(因为是静态的)
}
}
//一、饿汉式 直接初始化
class Bank{
//1、提供私有的构造器
private Bank(){
}
//2、由于构造器声明为私有的,所以只能在内部创建对象(恰好为对象的属性)
//5、静态方法中的属性需要是静态的
private static Bank bank = new Bank();
//3、由于内部创建的对象是私有的,需要提供可以调用的方法
//4、需要提供静态的方法(不用通过创建对象来调用)
public static Bank getBank(){
return bank;
}
}
//二、懒汉式 先不初始化后面在方法里面再初始化
class Person{
//1、私有化类的构造器,防止外部进行实例化
private Person(){
}
//2、声明当前类对象,没有初始化
//4、此对象也必须声明为static
private static Person person = null;
//3、声明public static的返回当前类对象的方法
public static Person getPerson(){
if(person == null){
person = new Person();
}
return person;
}
}
区分饿汉式 和 懒汉式:
饿汉式:
坏处:对象加载时间过长
好处:线程安全
懒汉式:
好处:延迟对象的创建
坏处:目前写法,线程不安全---->到多线程进行修改
应用场景:
6.2 main方法
main方法的使用说明:
1、作为程序的入口
2、也可以是普通的静态方法
3、作为和控制台交互的方式,之前是Scanner
修饰符:static final abstract native 可以修饰方法
6.3 类的成员之四:代码块
1、代码块的作用:用来初始化类、对象
2、代码块如果有修饰的话,只能使用static
3、分类:静态代码块 vs 非静态代码块
4、静态代码块
>内部可以有输出语句
>随着类的加载而执行(不是加载而加载),只执行一次
>作用:初始化类的信息(属性…)
>静态代码块先于非静态代码块执行
>只能调用静态的属性和方法,不能调用非静态结构(生命周期问题,先出生的不可以调用后出生的)
5、非静态代码块
>内部可以有输出语句
>随着对象的创建而执行,每创建一个对象就执行一次
>作用:可以在创建对象时,对对象的属性等进行初始化
>非静态代码块可以调用非静态和静态结构(生命周期问题,后出生的可以调用先出生的)
class Person{
//属性
int name;
//构造器
public Preson(){
}
//代码块
//静态代码块
static{
}
//非静态代码块
{
}
//方法
public void eat(){
}
}
class Person{
//静态代码块
//4、加载类的时候执行静态代码块
static{
System.out.println("1");
}
//非静态代码块
//6、执行完所有的静态代码块以后,先执行本类中的非静态代码块,再执行构造器
{
System.out.println("2");
}
//构造器
public Person(){
//3、这里省略了super(),继续向上调用,直到Object
//7、执行完所有的静态代码块以后,先执行非静态代码块,再执行构造器
System.out.println("3");
}
}
class Man extends Person{
//5、加载类的时候执行静态代码块
static{
System.out.println("4");
}
//8、执行完所有的静态代码块以后,先执行本类中的非静态代码块,再执行构造器
{
System.out.println("5");
}
public Man(){
//2、这里省略了super(),向上调用
//9、执行完所有的静态代码块以后,先本类执行非静态代码块,再执行构造器
System.out.println("6");
}
}
public class ManTest{
public static void main(String[] args){
new Man();//1、开始调用
//new Man();//new两次的话,静态的不再执行,其余一样
}
}
结果是:1 4 2 3 5 6
说明:静态代码块最先执行,非静态代码块先于构造器执行
总结:由父及子,静态先行
class Person{
//静态代码块
//2、加载类的时候执行静态代码块
static{
System.out.println("1");
}
//非静态代码块
//8、执行完所有的静态代码块以后,先执行非静态代码块,再执行构造器
{
System.out.println("2");
}
//构造器
public Person(){
//7、这里省略了super(),继续向上调用,直到Object
//9、执行完所有的静态代码块以后,先执行非静态代码块,再执行构造器
System.out.println("3");
}
}
class Man extends Person{
//3、加载类的时候执行静态代码块
static{
System.out.println("4");
}
//10、执行完所有的静态代码块以后,先执行非静态代码块,再执行构造器
{
System.out.println("5");
}
public Man(){
//6、这里省略了super(),向上调用
//11、执行完所有的静态代码块以后,先执行非静态代码块,再执行构造器
System.out.println("6");
}
//1、此时main方法是类Man里面的,所以先加载类,和上面的不一样
public static void main(String[] args){
//4、加载完类以后才到这里
System.out.println("7");
new Man();//5、开始调用
//new Man();//new两次的话,静态的不再执行,其余一样
}
}
结果:1 4 7 2 3 5 6
6.4 final关键字
final :最终的
1、final 可以用来修饰:类、方法、变量
2、final 用来修饰一个类:此类不能被其他类所继承,没有子类,“太监类”
如:String类、System类、StringBuffer类
3、final 修饰方法,此方法不能被重写
如:Object中的getClass();
4、final 用来修饰变量:此时的变量称为常量,用全大写来表示
4.1 修饰属性:可以考虑赋值的位置:显式初始化、代码块中初始化、构造器中初始化,总之就是在对象创建时final修饰的属性就必须已经有值了,且此时已经无法修改
4.2 修饰局部变量:使用final以后的形参称为常量,在方法体里面只能调用不可以重新赋值
引用类型的形参地址不可以变(不能new或者改变地址的操作),但是地址指向的内容可以改变,如形参的属性可以改变
static final 修饰属性:全局常量
6.5 抽象类与抽象方法
abstract 关键字的使用:
1、abstract : 抽象的
2、abstract 可以用来修饰的结构:类、方法
3、abstract 修饰类:抽象类
>此类不能实例化
>抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
>开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
4、abstract 修饰方法:抽象方法
>抽象方法只有方法的声明,没有方法体
>包含抽象方法的类,一定是一个抽象类,反之,抽象类中可以没有抽象方法的
>只有子类重写了抽象父类中的所有抽象方法后,此子类方可实例化
如果没有重写所有的抽象方法,则此子类也是抽象类(只有这样才不会报错),需要abstract修饰
abstract 注意点:
1、不能修饰属性、构造器等结构
2、不能修饰私有方法(不能被子类重写)、静态方法(非静态才考虑重写)、final的方法(不能被重写)、final的类 (没有子类)
匿名对象和匿名类:
//匿名对象
method(new Person());
//创建一个匿名子类的对象(是子类且类是匿名的)
//此时既有多态(左边是父类,右边是子类),又有创建子类对象
Person p = new Person(){ //方法体内就是新创建的这个子类的具体(重写)方法,只能用一次
//重写抽象父类中所有的抽象方法
@override
public void eat(){
System.out.println("吃饭");
}
}
//匿名子类匿名对象(类和对象都匿名)
method(new Person(){
//重写抽象父类中所有的抽象方法
@override
public void eat(){
System.out.println("吃饭");
}
});
设计模式
模板方法设计模式:使用抽象类
public class Template{
//可以确定的部分直接在父类里面写好
public void TimeTest(){
...
code();
...
}
//不能确定的部分抽象成抽象方法,在子类里面进行重写
public abstract void code();
}
6.6 接口(interface)
接口的使用:
1、接口使用interface来定义
2、Java中,接口和类是并列的两个结构
3、如何定义接口:定义接口的成员
3.1 JDK7以前:只能定义全局变量和抽象方法
>全局常量:public static final的,但是书写时,可以省略不写
>抽象方法:public abstract的,但是书写时,可以省略不写
3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
>全局常量:public static final的,但是书写时,可以省略不写
>抽象方法:public abstract的,但是书写时,可以省略不写
>静态方法:public static … 静态方法只能用接口去调用
>默认方法:public default … 通过实现类的对象来调用,也可以在实现类里面重写默认方法
知识点:1、如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法
那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法(优先父类)
2、 如果实现类实现了多个接口,而这多个接口定义了同名同参数的默认方法,那么在实现类没有重写的情况下报错–>接口冲突,这就需要在实现类里面重写此方法
3、如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
interface compare{
//接口中的默认方法
public default void method(){
}
}
class Person{
public void method(){
}
}
// 继承 父类 实现 接口
class Man extends Person implements compare{
public void method(){
}
public void useMethod(){
//子类本身重写的方法
method();
//调用父类的方法
super.method();
//调用接口中的默认方法
compare.super.method();
}
4、接口中不能定义构造器! 也就意味着接口是不可以实例化的
5、Java开发中,接口通过让类去实现**(implements)的方式来使用
如果实现类覆盖了(接口一般不说重写,而是说实现**)接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
6、Java类可以实现多个接口—>弥补Java中单继承性的局限性
先继承后接口
7、接口之间可以实现多继承
8、接口的具体使用体现多态性
9、接口,实际上可以看做是一种规范,(如果要…必须做到…),接口的实现集合就是驱动(Driver)
10、开发中体会面向接口的编程
//一个类可以实现多个接口
class Man extends Person implements Fly,Eat{
}
interface AA{
//全局变量
public static final int MAX = 1;
//抽象方法
public abstract Speed();
}
interface BB{
}
//接口之间多继承
interface CC extends AA,BB{
}
面试题:抽象类和接口有什么异同?
11、匿名对象和匿名类同上
设计模式
1、代理模式(Proxy)
//设计一个代理模式
public class NetWorkTest{
public static void main(String[] args){
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);//被代理的对象放入代理对象中
//通过代理类来调用被代理类的方法
proxyServer.brower();
}
}
//创建接口
interface NetWork{
public abstract void brower();
}
//被代理类,只需要写好自己核心想干的事情就好了,其余的事情就交给代理类完善补充,比如租房,客户和中介的关系
class Server implements NetWork{
public void brower(){
System.out.println("真实需要的联网客户");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork netWork;
//构造器
public ProxyServer(NetWork netWork){//体现接口的多态性,传入什么类就执行该类
this.netWork = netWork;
}
public void check(){
System.out.println("联网之前的检查");
}
public void brower(){
//先进行检查
check();
netWork.brower();//此处调用的是传入类的方法(被代理类)
}
}
2、工厂模式:
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的
创建者是工厂,调用者是具体的对象
6.7 类的成员之五:内部类
例子:人这个类中大脑就可以是内部类(不同于年龄、性别用变量就可以表示)
1、Java中允许将一个类A声明在另一个类B中,则类A就是内部类、类B就是外部类
2、内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
3、成员内部类:
3.1 作为外部类的成员
>调用外部类的结构(属性、方法…)
>可以被static修饰
>可以被4种不同的权限修饰符修饰
3.2 作为一个类
>类内可以定义属性、方法、构造器
>可以被final修饰,表示此类不能被继承。言外之意,不使用final就可以被继承
>可以被abstract修饰,作为抽象类
4、关注以下3个问题:
4.1 如何实例化成员内部类的对象
4.2 如何在成员内部类中区分调用外部类的结构
public class Person {
int age;
String name;
public void sing() {
}
//非静态内部类
class Bird{
int age;
public void display(int age) {
System.out.println(age);//方法的形参
System.out.println(this.age);//内部类的属性
System.out.println(Person.this.age);//外部类的属性
}
}
//静态内部类
static class Dog{
int age;
}
public static void main(String[] args) {
//静态内部类的实例化
Person.Dog dog = new Person.Dog();
//非静态内部类的实例化
Person p = new Person();
Person.Bird bird = p.new Bird();
}
}
4.3 开发中局部内部类的使用
//返回一个实现了Comparable接口的对象
public Comparable getComparable(){
//创建一个实现Comparable接口的类:局部内部类
class MyComparable implements Comparable{
@override
public int compareTo(Object o){
return 0;
}
}
return new MyComparable();
}
第七章 异常处理
子模块里面用throws 将异常抛出
主模块里面用try catch获取异常
第八章 多线程
8.1 基本概念:程序、进程、线程
并行:多个CPU同时执行多个任务,多个人同时做不同的事情
并发:一个CPU(时间片)同时执行多个任务,秒杀,多个人同时做一件事情
多线程优点:
8.2 线程的创建和使用
8.2.1 方法一:继承Thread类
1、创建一个继承于Thread类的子类
2、重写Thread类的run()方法
3、创建Thread类的子类对象
4、通过此对象调用start() ①启动当前线程 ②调用当前线程中的run方法
※※※※该方法创建多线程时,需要创建多个对象※※※※
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
//2、重写Thread类的run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println("偶数:" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3、创建子类对象
MyThread myThread = new MyThread();
//4、此对象调用start
myThread.start();
//再创建一个线程
MyThread myThread01 = new MyThread();
//4、此对象调用start
myThread01.start();
System.out.println("Hello");
}
}
如果只使用一次的话可以用匿名子类
//或者匿名子类(只用一次)
public class ThreadTest {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println("偶数:" + i);
}
}
}
}.start();
}
}
常用方法:
1、start() :启动当前线程,调用当前线程的run方法
2、run() :通常需要重写此方法,将要执行的操作声明在此方法中
3、Thread.currentThread() : 静态方法,返回执行当前代码的线程
4、getName() :获取当前线程的名字
5、setName() :设置当前线程的名字 在start之前设置 也可以通过构造器设置名字
6、yield() : 释放当前CPU的执行权
7、join() : 在线程A中调用线程B的join()方法,此时线程A就进入阻塞状态,开始运行线程B,直到线程B完全执行完成后,线程A才结束阻塞继续执行
8、stop() : 已过时
9、sleep(long millitime) : 让当前线程睡眠指定的时间
线程的优先级:
线程的优先等级:
MAX_PRIORITY=10 最高
MIN_PRIORITY=1 最低
NORM_PRIORITY=5 默认
方法:getPriority() 返回线程优先值
setPriority(int newPriority) : 改变线程的优先级 在start() 之前
说明:高优先级的线程要抢占低优先级线程cpu的执行权,但是只是从概率上讲,高优先级的高概率执行,并不是说只有高优先级的线程执行完后低优先级的才执行
8.2.2 方法二:实现Runnable接口
1、创建一个实现了Runnable接口的类
2、实现类去实现Runnable中的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 多态
5、通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() —>调用了Runnable类型的target的run
※※※※该方法创建多线程时,共用一个对象※※※※
//1、创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2、实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "偶数:" + i);
}
}
}
}
public class ThreadTestRunnable {
public static void main(String[] args) {
//3、创建实现类的对象
MThread mThread = new MThread();
//4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 多态
Thread t1 = new Thread(mThread);
//5、通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --->调用了Runnable类型的target的run
t1.start();
//再创建一个线程 这里不需要重新创建一个对象是因为这里只是调用了run方法
Thread t2 = new Thread(mThread);
t2.start();
}
}
8.2.3 比较两种方式
--------------------------------开发中优先选择:Runnable接口的方式--------------------------
原因:1、实现的方式没有类的单继承性的局限性
2、实现的方式更适合来处理多个线程有共享数据的情况(接口方式共用一个对象)
联系:Thread本身也是实现了Runnable的接口
相同点:两种方式都需要重写(实现)run()方法
8.3 线程的生命周期
8.4 线程同步
线程安全问题:多个线程之间共享数据,一个线程对数据进行处理是需要时间的,在这个时间里面另外一个线程也对共享数据进行操作就会产生安全问题。
在java中通过同步机制来解决安全问题
8.4.1 方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1、操作共享数据的代码,即为需要被同步的代码
2、同步监视器,俗称:锁 任何一个类的对象都可以充当锁
要求:多个线程必须共用一把锁
3、操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率降低—局限性
//实现接口的方式创建线程 共用一个对象
class Windows implements Runnable {
private int ticket = 50;//共享的数据
Object obj = new Object();//锁
@Override
public void run() {
while (true) {
synchronized (obj) {//或者用this代替
if (ticket > 0) {
try {
Thread.sleep(600);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
//继承的方式创建线程 需要创建多个对象
class Windows extends Thread {
private static int ticket = 50;//共享的数据 加static才是共享的
private static Object obj = new Object();//锁 静态变量保证唯一
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(600);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
8.4.2 方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,将此方法声明为同步的。
Runnable:
//实现接口的方式创建线程 共用一个对象
class Windows implements Runnable {
private int ticket = 50;//共享的数据
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){ //同步方法 锁就是this
if (ticket > 0) {
try {
Thread.sleep(600);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
}
}
}
继承:
//继承的方式创建线程 需要创建多个对象
class Windows extends Thread {
private static int ticket = 50;//共享的数据 加static才是共享的
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){ //同步方法 需要静态才是同一把锁Window.class 关键
if (ticket > 0) {
try {
Thread.sleep(600);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
}
}
}
总结:
1、同步方法仍然涉及到同步监视器(锁),只是不需要我们显式地声明
2、非静态的同步方法(接口类型里面),同步监视器是:this
静态的同步方法(继承Thread里面),同步监视器是:类本身
//线程安全的 单例 懒汉式
public class BankTest{
}
class Bank{
private Bank(){}
private static Bank instance = null;
//此处会出现共享数据的线程安全问题,直接声明为同步方法 此处的锁就是Bank.class本身
public static synchronized Bank getInstance(){
if(instance == null){
instance = new Bank();
}
return instance;
}
//或者 同步代码块 效率稍低
public static Bank getInstance(){
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
return instance;
}
//或者 同步代码块 效率高一点
public static Bank getInstance(){
if(instance == null){ //这样就可以让后来的线程不需要再进入到同步代码块里面,直接拿到对象就走,提高效率
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
}
return instance;
}
}
}
8.4.3 线程的死锁
理解:不同的线程分别占用对方需要的同步资源不放弃(同一个锁),都在等待对方放弃自己需要的同步资源,就形成了死锁
说明:
1、出现死锁以后不会出现异常,不会提示,只是所有线程都处于阻塞状态,无法继续
2、使用同步时避免出现死锁
8.5 方式三:Lock锁
JDK5.0新增
1、实例化ReentrantLock
2、在try内调用lock()
3、在finally内调用unlock()
class Windows1 implements Runnable {
private int ticket = 100;//共享的数据
//1、实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//2、调用lock()
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
} else {
break;
}
} finally {
//3、调用unlock()
lock.unlock();
}
}
}
}
8.5.1 synchronized和lock比较
相同:都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
优先顺序:Lock --> 同步代码块 —>同步方法
例子:
//直接将账户定义为对象 体会面向对象的编程思想
class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public synchronized void deposit(int money) {//同步方法
if (money > 0) {
balance += money;
System.out.println(Thread.currentThread().getName() + ":存入了" + money + " 此时账户的余额是:" + balance);
}
}
}
//实现Runnable接口
class Bank implements Runnable {
private Account account;
public Bank(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class BankTry {
public static void main(String[] args) {
//实例化账户和银行
Account account = new Account(0);
Bank bank = new Bank(account);
//创建线程(两个存钱的人)
Thread t1 = new Thread(bank);
Thread t2 = new Thread(bank);
t1.setName("甲");
t2.setName("乙");
//开启线程
t1.start();
t2.start();
}
}
8.6 线程的通信
wait() —notify()/notifyAll()
实现交替打印:
@Override
public void run() {
while (true) {
synchronized (obj) {
obj.notify(); //进来的线程唤醒上一个阻塞的线程 同步监视器调用
if (ticket > 0) {
try {
Thread.sleep(600);//阻塞的时间长一点才更能体现同步机制的作用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "票号是:" + ticket);
ticket--;
try {
obj.wait(); //线程阻塞在这里,但是此时会释放锁 同步监视器调用
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
-----------------------------------------------------说明:------------------------------------------------------------------------------------
1、wait() notify() notifyAll() 三者都只能用在同步代码块或者同步方法中,lock里面不行
2、wait() notify() notifyAll() 的调用者都必须是同步监视器(锁)
3、wait() notify() notifyAll() 三者是定义在java.lang.Object类中的!(与前述同步监视器是任意类的对象相吻合)
面试题:sleep() 和wait()的异同
相同点:一旦执行都可以使当前线程进入阻塞状态
不同点:1、两个方法声明的位置不同,sleep()声明在Thread类中,wait()声明在Object类中
2、调用的要求不同:sleep()可以在任意位置调用,wait()只能在同步代码块或者同步方法里面调用(主要是需要有同步监视器存在)
3、释放同步监视器:两者均在同步代码块或者同步方法里面调用的时候,sleep()不释放同步监视器,wait()会释放同步监视器
//店员对象
class Clerk {
private int product = 0;
//生产产品
public synchronized void produceProduct() {
if (product < 20) {
product++;//先加是因为输出从1开始
System.out.println(Thread.currentThread().getName() + ":生产第" + product + "个产品");
notify();//激活消费线程
} else {
try {
wait();//当产品大于20的时候就阻塞线程 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if (product > 0) {
System.out.println(Thread.currentThread().getName() + ":消费第" + product + "个产品");
product--;//后减 取走才减
notify();//激活生产线程
} else {
try {
wait();//当产品大于20的时候就阻塞线程 释放锁给生产者线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":开始生产....");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
//消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":开始消费....");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProducerConsumeTest {
public static void main(String[] args) {
//实例化店员 消费者 生产者
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
//创建线程
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
//设置线程名称
t1.setName("生产者1:");
t2.setName("消费者1:");
//开启线程 调用run方法
t1.start();
t2.start();
}
}
8.7 新增的线程创建方法
8.7.1 方法三:实现Callable接口
与Runnable接口相比,Callable功能更加强大:
1、call()方法有返回值 类比与run方法
2、方法可以抛出异常
3、支持泛型的返回值
4、需要借助FutureTask类,获取call方法返回值的结果
步骤:
1、创建一个实现Callable的实现类
2、实现call方法,将此线程需要执行的操作声明在call中
3、创建Callable接口实现类的对象
4、将此Callable接口实现类的对象作为参数传递到FutureTask类的构造器中,创建FutureTask对象
5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建线程,并启动
6、获取Callable中的call方法返回值 (对返回值感兴趣的话再调用)
//1、创建一个实现Callable的实现类
class NumThread implements Callable {
//2、实现call方法,将此线程需要执行的操作声明在call中
@Override
public Object call() throws Exception { //有返回值
int sum = 0;
for (int i = 0; i < 100; i++) {
System.out.println(i);
sum += i;
}
return sum; //返回值
}
}
public class CallableTest {
public static void main(String[] args) {
//3、创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4、将此Callable接口实现类的对象作为参数传递到FutureTask类的构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);//借助FutureTask类的实例获取Call方法的返回值
//5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建线程,并启动
//启动线程 FutureTask也实现了Runnable接口
new Thread(futureTask).start();
try {
//6、获取Callable中的call方法返回值
Object sum = futureTask.get(); //获取返回值 对返回值感兴趣才调用get方法
System.out.println("返回值是:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解Callable接口比Runnable接口创建线程方式强大:
1、call()方法可以有返回值
2、call()方法可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型的
8.7.2 方法四:线程池
步骤:
1、提供指定线程数量的线程池
2、执行指定的线程操作 需要提供实现Runnable接口或Callable接口的实现类
3、关闭线程池
//创建实现Runnable接口的类
class NumRun implements Runnable{
@Override
public void run() {
//打印偶数
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
//创建实现Callable接口的类
class NumCall implements Callable{
@Override
public Integer call() throws Exception {
int sum = 0;
//打印奇数 返回总和
for (int i = 0; i <= 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
return sum;
}
}
public class ThreadPool {
public static void main(String[] args) {
//1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //强制类型转换 向下转型
service1.setCorePoolSize(15); //设置线程池大小...
//2、执行指定的线程操作 需要提供实现Runnable接口或Callable接口的实现类
service.execute(new NumRun()); //适用于Runnable接口
service.submit(new NumCall());//适用于Callable接口
//3、关闭线程池
service.shutdown();
}
}
第九章 java常用类
1、字符串相关的类 --String
第十五章 java反射机制
Person.java
package com.reflect.java;
public class Person {
private String name;//私有属性
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {//私有
this.name = name;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println("我是person!");
}
private String showNation(String nation){//私有
System.out.println("国籍是" + nation);
return nation;
}
}
ReflectionTest.java
package com.reflect.java;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
//反射之前
@Test
public void test1(){
//1、创建person类的对象
Person p1 = new Person("Tom",12);
//调用属性和方法
p1.age = 10;
p1.show();
//不可以调用私有的属性和方法
}
//反射之后
@Test
public void test2() throws Exception {
Class clazz = Person.class;
//1、通过反射,创建对象
Constructor cons = clazz.getConstructor(String.class, int.class);
Object obj = cons.newInstance("Tom", 12);
Person p = (Person)obj;
System.out.println(p.toString());
//2、通过反射,调用对象指定的属性和方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p,10);
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
//反射可以调用私有的构造器,方法,属性
//调用私有的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);//对应一个参数的那个私有构造器
cons1.setAccessible(true);
Object p1 = (Person)cons1.newInstance("Jerry");
//调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"hanmeimei");
System.out.println(p1);
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1,"中国");//invoke-调用
}
}
获取Class实例的方式:(四种)
//获取Class的实例的方式 四种方式获取到的是同一个Class实例
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
//方式二:通过运行时类的对象
Person p11 = new Person("tom",12);
Class clazz2 = p11.getClass();
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.reflect.java.Person");//哪一个包的Person
//(了解)方式四:使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.reflect.java.Person");
}
创建运行时类的对象:newInstance()
@Test
public void test4() throws InstantiationException, IllegalAccessException {
Class clazz = Person.class;
Person obj = clazz.newInstance();
}
反射的动态性:
@Test
public void test5() throws Exception {
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.reflect.java.Person";
break;
}
Object instance = getInstance(classPath);
System.out.println(instance);
}
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
p = (Person)obj;
System.out.println(p.toString());
//2、通过反射,调用对象指定的属性和方法
//调用属性
Field age = clazz.getDeclaredField("age");
age.set(p,10);
//调用方法
Method show = clazz.getDeclaredMethod("show");
show.invoke(p);
//反射可以调用私有的构造器,方法,属性
//调用私有的构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);//对应一个参数的那个私有构造器
cons1.setAccessible(true);
Object p1 = (Person)cons1.newInstance("Jerry");
//调用私有的属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"hanmeimei");
System.out.println(p1);
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
String nation = (String) showNation.invoke(p1,"中国");//invoke-调用
}
}
获取Class实例的方式:(四种)
//获取Class的实例的方式 四种方式获取到的是同一个Class实例
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
//方式二:通过运行时类的对象
Person p11 = new Person("tom",12);
Class clazz2 = p11.getClass();
//方式三:调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.reflect.java.Person");//哪一个包的Person
//(了解)方式四:使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.reflect.java.Person");
}
创建运行时类的对象:newInstance()
@Test
public void test4() throws InstantiationException, IllegalAccessException {
Class clazz = Person.class;
Person obj = clazz.newInstance();
}
反射的动态性:
@Test
public void test5() throws Exception {
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch (num){
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.reflect.java.Person";
break;
}
Object instance = getInstance(classPath);
System.out.println(instance);
}
public Object getInstance(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
未完待续。。。



