Java 基础总结 (分知识点、带目录)

内容来自多个帖子,花了几天整理,将就地看吧

一、Java 概述

1991 年Sun公司的James Gosling(詹姆斯·高斯林)等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒、PDA等的微处理器;
1994年将Oak语言更名为Java;

1 Java 的三种技术架构

JAVAEE:Java Platform Enterprise Edition,开发企业环境下的应用程序,主要针对web程序开发;
JAVASE:Java Platform Standard Edition,完成桌面应用程序的开发,是其它两者的基础;
JAVAME:Java Platform Micro Edition,开发电子消费产品和嵌入式设备,如手机中的程序;

2 JDK 和 JRE

1,JDK:Java Development Kit ,java的开发和运行环境,java的开发工具和jre。
2,JRE:Java Runtime Environment,java程序的运行环境,java运行的所需的类库

3 Java 环境配置

配置环境变量:让java jdk\bin目录下的工具,可以在任意目录下运行,原因是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮我们去找指定的目录。

环境变量的配置:
1):永久配置方式:
JAVA_HOME=%安装路径%\Java\jdk
path=%JAVA_HOME%\bin
2):临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin
特点:系统默认先去当前路径下找要执行的程序,如果没有,再去path中设置的路径下找。

设置JAVA_HOME变量的作用
一、方便引用,比如,JDK安装在D:\Program Files (x86)\Java\jdk1.7.0目录里,则设置JAVA_HOME为该目录路径, 那么以后要使用这个路径的时候, 只需输入%JAVA_HOME%即可, 避免每次引用都输入很长的路径串;
二、归一原则, 当JDK路径被迫改变的时候, 仅需更改JAVA_HOME的变量值即可。
三、第三方软件会引用约定好的JAVA_HOME变量, 典型的就是tomcat服务器,如果不配置JAVA_HOME变量,那么tomcat服务器根本运行不起来

path 变量
PATH 环境变量。作用是指定命令搜索路径,在命令行下面执行命令如 javac 编译java程序时,它会到PATH 变量所指定的路径中查找看是否能找到相应的命令程序。我们需要把jdk安装目录下的bin目录增加到现有的 PATH 变量中,bin目录中包含经常要用到的可执行文件如 javac/java/javadoc等待,设置好PATH变量后,就可以在任何目录下执行 javac/java 等工具了。
操作系统用path的路径来找可执行程序(.exe等程序),在windows下当我们敲任何一个命令时,实际上就是在执行一个可执行文件,如输入notepad命令就可以打开一个记事本。

例:在cmd下输入notepad命令就可以打开一个记事本。
notepad.exe所在的Windows目录已经添加到了Path环境变量中,实际上是执行了c:\Windows下的notepad.exe这个可执行文件

classpath的配置:
1):永久配置方式:classpath=.;c:;e:
2):临时配置方式:set classpath=.;c:;e:
注意:在定义classpath环境变量时,需要注意的情况:

  • 如果没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件;
  • 如果指定了classpath,那么会在指定的目录下查找要运行的类文件。
    还会在当前目录找吗?两种情况:
    1):如果classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。
    2):如果classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。
    一般不指定分号,如果没有在指定目录下找到要运行的类文件,就报错,这样可以调试程序。

设置Classpath的目的,在于告诉Java执行环境,在哪些目录下可以找到您所要执行的Java程序(.class文件),关于这个ClassPath变量,其实可以不用配置了,在网上经常看到Classpath=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\toos.jar,但学习java这么久发现,不配置ClassPath也不影响java项目的开发和运行的。

使用javac命令编译.java文件时,如果需要其他的类,也是通过classpath去找的,
使用java命令执行.class文件时,执行的.class文件是通过classpath去找的。
classpath表示的是要查找的类所在的路径。

4 javac命令和java命令

要知道java是分两部分的:一个是编译,一个是运行。
javac:负责的是编译的部分,当执行javac时,会启动java的编译器程序。对指定扩展名的.java文件进行编译。生成了jvm可以识别的字节码文件。也就是class文件,也就是java的运行程序。
java:负责运行的部分.会启动jvm.加载运行时所需的类库,并对class文件进行执行.
一个文件要被执行,必须要有一个执行的起始点,这个起始点就是main函数.

二、Java基础语法

1 关键字

关键字:其实就是某种语言赋予了特殊含义的单词。
保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词。
关键字总览
在这里插入图片描述

详情:java关键字及其作用

2 标识符

标识符:其实就是在程序中自定义的名词。比如类名,变量名,函数名。包含 0-9、a-z、$、_ ;
注意
1)不可以以数字开头。
2)不可以使用关键字。
在这里插入图片描述

3 常量 和 变量

常量:是在程序中的不会变化的数据。

变量:其实就是内存中的一个存储空间,用于存储常量数据。
作用:方便于运算。因为有些数据不确定。所以确定该数据的名词和存储空间。
特点:变量空间可以重复使用。
什么时候定义变量? 只要是数据不确定的时候,就定义变量。

变量空间的开辟需要什么要素呢?
1,这个空间要存储什么数据?数据类型
2,这个空间叫什么名字啊?变量名称
3,这个空间的第一次的数据是什么? 变量的初始化值

变量的作用域和生存期:
变量的作用域: 作用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期: 变量从定义的位置开始就在内存中活了;变量到达它所在的作用域的时候就在内存中消失了;

4 数据类型

1):基本数据类型:byte、short、int、long、float、double、char、boolean
2):引用数据类型: 数组、类、接口。
在这里插入图片描述
级别从低到高为: byte,char,short(这三个平级)–>int–>float–>long–>double
自动类型转换:从低级别到高级别,系统自动转的;
强制类型转换:什么情况下使用把一个高级别的数赋给一个别该数的级别低的变量;

unicode码范围
汉字:[\u4e00,\u9fa5]
数字:[0x30,0x39]
小写字母:[0x61,0x7a]
大写字母:[0x41,0x5a]

5 运算符

在这里插入图片描述

  1. 算术运算符。
    符号 + - * / %
    %:任何整数模2不是0就是1,所以只要改变被模数就可以实现开关运算。
    +:连接符。
    ++,–-
  2. 赋值运算符。
    = ,+=, -=, *=, /= ,%=
  3. 比较运算符。
    特点:该运算符的特点是:运算完的结果,要么是true,要么是false。
  4. 逻辑运算符。
    & , | , ^
    ! ,&& , ||
    逻辑运算符除了 ! 外都是用于连接两个boolean类型表达式。
  • &: 只有两边都为true结果是true。否则就是false。
  • |:只有两边都为false结果是false,否则就是true
  • ^:异或:和或有点不一样。
    两边结果一样,就为false。
    两边结果不一样,就为true.
  • & 和 &&区别:
    & :无论左边结果是什么,右边都参与运算
    &&:短路与,如果左边为false,那么右边不参数与运算。
  • | 和|| 区别:
    |:两边都运算。
    ||:短路或,如果左边为true,那么右边不参与运算。
  1. 位运算符:用于操作二进制位的运算符。
    & | ^
    & << >> >>>(无符号右移)

练习:对两个变量的数据进行互换。不需要第三方变量。

int a  = 3,b = 5;-->b = 3,a = 5;
a = a + b; a = 8;
b = a - b; b = 3;
a = a - b; a = 5;

a = a ^ b;//
b = a ^ b;//b = a ^ b ^ b = a
a = a ^ b;//a = a ^ b ^ a = b;

练习:高效的算出 2*8 = 2<<3;

6 语句

If、 switch 、do while 、while 、for
1)、当判断固定个数的值的时候,可以使用if,也可以使用switch。
2)、当判断数据范围,获取判断运算结果boolean类型时,需要使用if。
3)、当某些语句需要执行很多次时,就用循环结构。 while和for可以进行互换。

  • 区别在于:如果需要定义变量控制循环次数。建议使用for。因为for循环完毕,变量在内存中释放。

break:作用于switch ,和循环语句,用于跳出,或者称为结束。
break语句单独存在时,下面不要定义其他语句,因为执行不到,编译会失败。当循环嵌套时,break只跳出当前所在循环。要跳出嵌套中的外部循环,只要给循环起名字即可,这个名字称之为标号。
continue:只作用于循环结构,继续循环用的。
作用:结束本次循环,继续下次循环。该语句单独存在时,下面不可以定义语句,执行不到。

7 访问修饰符

java中的访问修饰符
public: 所有
protected: 自己、同一包中、子类可以访问
default: 默认什么都不加,就是default,自己、同一包中可以访问,但是比protected级别要低即限制更多
private: 自己

8 函数

函 数:为了提高代码的复用性,可以将其定义成一个单独的功能,该功能的体现就是java中的函数。

函数定义的格式

修饰符 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数1,…){
 执行语句;
 return 返回值;
}

没有具体的返回值时,返回的返回值类型用void关键字表示。
如果函数的返回值类型是void时,return语句可以省略不写的,系统会帮你自动加上。
return的作用:结束函数。结束功能。
如何定义一个函数
函数其实就是一个功能,定义函数就是实现功能,通过两个明确来完成:
1)明确该功能的运算完的结果,其实是在明确这个函数的返回值类型。
2)在实现该功能的过程中是否有未知内容参与了运算,其实就是在明确这个函数的参数列表(参数类型&参数个数)。

函数的作用
1)、用于定义功能。
2)、用于封装代码提高代码的复用性。
注意:函数中只能调用函数,不能定义函数。

主函数:
保证该类的独立运行。因为它是程序的入口。因为它在被jvm调用。

主函数的构成
Public:访问权限最大。
static:不需要对象,直接类名即可。
void:主函数没有返回值。
Main:主函数特定的名称。
(String[] args):主函数的参数,是一个字符串数组类型的参数,jvm调用main方法时,传递的实际参数是 new String[0]。
jvm默认传递的是长度为0的字符串数组,我们在运行该类时,也可以指定具体的参数进行传递。可以在控制台,运行该类时,在后面加入参数。参数之间通过空格隔开。jvm会自动将这些字符串参数作为args数组中的元素,进行存储。

函数定义名称是为什么呢?
1)、为了对该功能进行标示,方便于调用
2)、为了通过名称就可以明确函数的功能,为了增加代码的阅读性

重载
重载的定义是:在一个类中,如果出现了两个或者两个以上的同名函数,只要它们的参数的个数,或者参数的类型不同,即可称之为该函数重载了。
当函数同名时,只看参数列表。和返回值类型没关系。

如何区分重载:当函数同名时,只看参数列表。和返回值类型没关系。

覆盖:当子类重写父类中的方法时,若子类方法的参数列表与父类完全相同,则表示子类覆盖了父类的该方法。

重载的条件
a)发生在同一个类或者父子类之间
b)方法名称相同,参数列表(参数的个数,参数的数据类型、参数的顺序)不同
c)返回值无所谓
d)访问权限无所谓
歧义重载:

           ma(12,34)//歧义重载,编译错误
           public/void ma(int i,double j){//打印1}
           public/void ma(double i,int j){//打印2}

构造器重载:
this:
this.成员(字段、方法):调用本类的字段、方法
this(参数列表):调用本类的其他构造方法,必须在构造方法的第一句

                  public Person(int age){
                        this(age,””);//调用下面那个构造器
                    }
                  public Person(int age,String name){
                        this.age= age;//调用本类的字段
                        this.name= name;
                    }

super:父类的对象
super.成员:调用父类的成员(方法、字段)
super(参数列表):调用父类的构造方法,必须位于子类构造方法的第一句

  • 如果父类中没有无参的构造器,在子类中必须显式的调用父类带参数的构造方法(super(参数类表或者this(参数列表)));
  • 如果父类有无参的构造器,则子类中可以不用显式调用父类的构造器,因为系统默认调用super()(在子类构造器的第一句位置);

重写父类的成员(方法) 多态
重写的条件 :
a)发生在父子类之间
b)方法名称相同,参数列表(参数的个数,参数的数据类型、参数的顺序)相同
c)返回值必须相同
d)子类覆盖方法的访问权限大于等于父类被覆盖方法的访问权限
e)子类不能抛出比父类异常更大的异常(RuntimeException例外)

三、常用类

1 数组

用于存储同一类型数据的一个容器。好处:可以对该容器中的数据进行编号,从0开始。数组用于封装数据,就是一个具体的实体。

  • 数组可以看成是多个相同类型数据组合,对这些数据的统一管理。
  • 数组变量属引用类型,数组也可以看成是对象,数组中的每个元素相当于该对象的成员变量。
  • 数组的元素可以是任何数据类型,包括基本类型和引用类型。
  • C和C++中的数组都可以分配在栈上面,而JAVA中的数组是只能分配在堆上面的,因为JAVA中的数组是引用类型。

数组的创建与初始化
一维数组的声明方式有2种:

  • 格式一:数组元素类型 数组名[ ]; 即type var[ ];
  • 格式二:数组元素类型[ ] 数组名; 即type[ ] var;

一维数组的初始化化:

  • 动态初始化:数组定义与为数组元素分配空间和赋值的操作分开进行。
	Date days = new Date[3]; 
	days[0] =new Date(1,1,2020);
  • 静态初始化
int a[ ] = { 3, 9, 8};
Date days[ ] = {
   new Date(1, 4, 2004),
    new Date(2 ,4 ,2004),
    new Date(3 ,4, 2004)
};

二维数组

	int[][] a = new int[3][];//a : int[] 类型的 数组 ,该数组有3个元素
	a[0] = new int[2]; //a 的元素 a[0] 的数据类型为 int 类型的数组,空间大小为2

2 String

数组、集合、字符串之间的转换
a. Arrays.asList() //将数组转换成集合
例:List list = Arrays.asList(names);//将数组转换成集合。

b.String.valueOf() //将数组转换成字符串、也可将整数转换成字符串
例:char []ch1 = {‘a’,‘c’,‘a’,‘b’,‘a’};
String str2 = String.valueOf(ch1);
例:int num = 123456;
String str =String.valueOf(num);

c.toCharArray()将字符串转换成数组
例:String str = “gdfgdfgd”;
char[] ch = str.toCharArray();

d.Arrays.toString()将字符数组变成字符串特定格式输出,如[1,2,3]
例:String str1 = Arrays.toString(ch);
System.out.println(str1);

e.str.reverse();将字符串反转
例:StringBuffer sb =new StringBuffer(“adfsdsd”);
sb.reverse();

replace();函数
例1://将字符串"abcdabcdabdcadcbadcb"的字符出现的次数输出

String num = "abcdabcdabdcadcbadcb";
String t = num.replace("a","");//将所有的a转化为空字符--消去字符a
System.out.println(num.length()-t.length());

replaceAll(),用正则表达式
例2://将字符串中的数字去除

String num1 = "ab23c7da45bc5da65bd5c67ad7cb9adcb";
String t1 = num1.replaceAll("[0-9]", "");
System.out.println(t1);

例3://计算字符串的长度,一个字符算两个

       byte[]b =str.getBytes();
       System.out.println(b.length);//若出现一个汉字代表3个字节的情况,将Utf-8换成GBK就可以了
       String m = str.replaceAll("[\u4e00-\u9fa5]", "xx");
       System.out.println(m.length());

String类型是否可以被继承,为什么?
不能被继承,因为是被final修饰的类

String类型是否是基本类型,比较String类型变量时,== 和equals有何区别?
不是,是引用类型

  • 如果 String类型是直接赋值 == 和 equals 效果相同
    String a = “abc “;
    String b = “abc”;
    a == b 和 a.equals(b)都为true
  • 如果String是通过new构建的 == 和equals效果不同
    String a = new String(“abc”);
    String b = new String(“abc”);
    a ==b 结果为false
    a.equals(b)结果为true

char类型中是否可以存放一个汉字,为什么?
可以,char类型中存放的数据以Unicode码存放。

string中的方法:
charAt() 方法:一般与for循环一起,遍历字符串

Stringstr = “Hello Java,Hello World”;
     for(int i=0;i<str.length();i++){
         System.out.println(charAt(i));
     }

concat() 方法;拼接字符串,将指定字符串连接到此字符串的结尾
str.concat(“s”); //得到Hello Java,Hello Worlds
startWith() 方法;测试此字符串是否已指定的前缀开始,boolean
str. startWith(“Hello”); //true
format() 方法:将字符串按照某种格式输出
int i=1;int j=2; System.out.println(String.format("%d+%d=%d",i,j,i+j));//1+2=3
indexOf(String) 方法:输出字符串第一次出现的索引,没有的话输出-1
System.out.println(str.indexOf("Hello"));//0
lastIndexOf() 方法:输出字符串最后一次出现的索引
replace(old String, new String) 方法:替换字符串
split() 方法 :分割字符串
substring(int start,int end) 方法:截取字符串
toCharArray() 方法:变成字符数组
trim() 方法:去掉字符串两端的空格,字符串中间的去不掉
toUpperCase() 方法:将字符串转化为大写

StringBuffer和StringBuilder区别
StringBuffer线程安全的,同步的,效率比较低,JDK1.0之后出现
StringBuilder线程不安全,不同步的,效率比较高,JDK1.5之后出现

3 Object

Object:引用类型的根,是所有类的超类,是java中唯一一个没有父类的类

1、toString():将对象转成字符串,默认返回的格式:类名@哈希值 = getClass().getName() + “@” + Integer.toHexString(hashCode())
2、 boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。
equals()和 == 的区别:
==:可以判断基本数据类型和引用数据类型

  • 对于引用数据类型,表示两个对象的引用是否相等,即栈里面引用的地址是否相等
  • 对于基本数据类型,表示两个变量的数值是否相等

equals:可以判断引用数据类型

  • 引用数据类型:表示两个对象的引用是否相等,但是包装类、String、Date、File除外(这些判断的是对象的值是否相等,即堆内存里面的值是否相等)

3. hashcode() 返回的是对象的内存地址对应的int类型数据,但是我们可以重写hashCode()方法,返回的就不再是内存地址。
hashcode()相同的对象,这两个对象不一定相同;如果两个对象相同,则hashcode()一定相同。因此,重写equals方法必须先重写hashcode方法

4、Class getClass():获取任意对象运行时的所属字节码文件对象。

通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。

4 Math

Math方法

    Math.ceil(2.4);//3取比值大的最小整数
    Math.ceil(-2.4);//-2
    Math.floor(2.5);//2取比值小的最大整数
    Math.round(2.5);//3四舍五入
    Math.round(-2.5);//-2
    Math.random()//范围是[0,1)

获取随机数的方法

  1. **Math.random()**获取[0,1)的值,取不到1;
    用法:(int)Math.random()*n+1;取到1到n之间的整数
  2. Random r = new Random();
    int n = r.nextInt(n)+1;

5 Date

date与String 格式的时间转换

//SimpleDateFormat类:改变时间的格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
//将日期格式转换成指定的字符串
String time = sdf.format(date);
System.out.println(time);//现在的时间以yyyy/MM/dd HH:mm:ss格式输出
 
//将格式化的字符串转换成日期对象
Date date = sdf.parse(1995/07/12 03:00:00);
System.out.println(date);

6 File

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

构造方法

  • public File(String pathname) :通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例。
  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。
  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

常用方法
1 获取功能的方法

  • public String getAbsolutePath() :返回此File的绝对路径名字符串。
  • public String getPath() :将此File转换为路径名字符串。
  • public String getName() :返回由此File表示的文件或目录的名称。
  • public long length() :返回由此File表示的文件的长度。

** 绝对路径和相对路径**

  • 绝对路径:从盘符开始的路径,这是一个完整的路径。
  • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。

2 判断功能的方法

  • public boolean exists() :此File表示的文件或目录是否实际存在。
  • public boolean isDirectory() :此File表示的是否为目录。
  • public boolean isFile() :此File表示的是否为文件。

3 创建、删除功能的方法

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。
  • public boolean delete() :删除由此File表示的文件或目录。
  • public boolean mkdir() : 创建由此File表示的目录。
  • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。
         // 创建一级目录
        File f2= new File("newDir");    
        System.out.println("是否存在:"+f2.exists());// false
        System.out.println("是否创建:"+f2.mkdir());    // true
        System.out.println("是否存在:"+f2.exists());// true
        
   		// 创建多级目录
        File f3= new File("newDira\\newDirb");
        System.out.println(f3.mkdir());// false
        File f4= new File("newDira\\newDirb");
        System.out.println(f4.mkdirs());// true

目录的遍历

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

7 Properties

java.util.Properties 继承于Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时,System.getProperties 方法就是返回一个Properties对象。

构造方法

  • public Properties() :创建一个空的属性列表。

基本的存储方法

  • public Object setProperty(String key, String value) : 保存一对属性。
  • public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
  • public Set<String> stringPropertyNames() :所有键的名称的集合。

与流相关的方法

  • public void load(InputStream inStream): 从字节输入流中读取键值对。
    参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式:
	filename=a.txt
	length=209385038
	location=D:\a.txt

小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。

四、Java 内存管理

Java运行时数据区域

在这里插入图片描述

java分了5片内存。

1:寄存器。2:本地方法区。3:方法区。4:栈。5:堆。
:存储的都是局部变量 ( 函数中定义的变量,函数上的参数,语句中的变量 );
只要数据运算完成所在的区域结束,该数据就会被释放。
:用于存储数组和对象,也就是实体。啥是实体啊?就是用于封装多个数据的。

1:每一个实体都有内存首地址值。
2:堆内存中的变量都有默认初始化值。因为数据类型不同,值也不一样。
3:垃圾回收机制。

垃圾回收机制回收的是堆里面的内存,栈里面的数据自动入栈自动出栈

引用类型的数据在堆当中,内存中操作的只有栈,new关键字在堆里面分配一块内存

String是不可变字符,即一旦分配了内存,此内存的值将不会改变,又将String赋值是,会重新分配一块内存,字符串池: StringBuffer
在堆中的东西叫对象

对象访问过程

在Java语言中,对象访问是如何进行的?对象访问在Java语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:
Object obj = new Object();
假设这句代码出现在方法体中,那“Object obj”这部分的语义将会反映到Java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”这部分的语义将会反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
例子:Person tom = new Person(1, 25)
在这里插入图片描述

五、面向对象

1 基本介绍

面向对象的理解

面向对象和面向过程的思想有着本质上的区别, 作为面向对象的思维来说,当你拿到一个问题时,你分析这个问题不再是第一步先做什么,第二步再做什么,这是面向过程的思维,你应该分析这个问题里面有哪些类和对象,这是第一点,然后再分析这些类和对象应该具有哪些属性和方法。这是第二点。最后分析类和类之间具体有什么关系,这是第三点。
面向对象的基本思想是,从现实世界中客观存在的事物出发来构造软件系统,并在系统的构造中尽可能运用人类的自然思维方式。
面向对象更加强调运用人类在日常生活的逻辑思维中经常采用的思想方法与原则,如抽象、分类,继承、聚合、多态等。

特点

1:将复杂的事情简单化。
2:面向对象将以前的过程中的执行者,变成了指挥者。
3:面向对象这种思想是符合现在人们思考习惯的一种思想。
过程和对象在我们的程序中是如何体现的呢?过程其实就是函数;对象是将函数等一些内容进行了封装。

特征

(1)封装
(2)继承
(3)多态性
详情

2 类

在类中定义其实都称之为成员。成员有两种:
1:成员变量:其实对应的就是事物的属性。
2:成员函数:其实对应的就是事物的行为。
所以,其实定义类,就是在定义成员变量和成员函数。但是在定义前,必须先要对事物进行属性和行为的分析,才可以用代码来体现。

成员变量

私有的成员变量:其他类不能直接创建对象访问,所以只有通过本类对外提供具体的访问方式来完成对私有的访问,可以通过对外提供函数的形式对其进行访问。
好处:可以在函数中加入逻辑判断等操作,对数据进行判断等操作。
总结:开发时,记住,属性是用于存储数据的,直接被访问,容易出现安全隐患,所以,类中的属性通常被私有化,并对外提供公共的访问方法。
这个方法一般有两个,规范写法:对于属性 xxx,可以使用setXXX(),getXXX()对其进行操作。

成员变量和局部变量的区别:
1:成员变量直接定义在类中。
局部变量定义在方法中,参数上,语句中。
2:成员变量在这个类中有效。
局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。
3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。

成员函数

重载
覆盖
静态函数

构造函数

构造函数:用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。
特点
1:该函数的名称和所在类的名称相同。
2:不需要定义返回值类型。
3:该函数没有具体的返回值。

记住:所有对象创建时,都需要初始化才可以使用。
注意事项:一个类在定义时,如果没有定义过构造函数,那么该类中会自动生成一个空参数的构造函数,为了方便该类创建对象,完成初始化。如果在类中自定义了构造函数,那么默认的构造函数就没有了。
一个类中,可以有多个构造函数,因为它们的函数名称都相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现的

构造函数、一般函数、构造代码块 有什么区别呢?
1:构造函数是在对象创建时,就被调用,用于初始化,而且初始化动作只执行一次。
2:一般函数,是对象创建后,需要调用才执行,可以被调用多次。
3:构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。
执行顺序:(优先级从高到低。)静态代码块>mian方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。

public class Test {
    static { System.out.println("a"); }
    {System.out.println("c"); }
    Test(){ System.out.println("d"); }
    public static void main(String[] args) {
        System.out.println("b");
        Test test = new Test();
    }
}

结果:abcd

什么时候使用构造函数呢?
分析事物时,发现具体事物一出现,就具备了一些特征,那就将这些特征定义到构造函数内。

构造代码块和构造函数有什么区别?
构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块,只要对象一建立,就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化,它具有针对性。

3 对象

创建一个对象都在内存中做了什么事情?
Person p = new Person("tom",18);
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)

4 内部类

内部类:如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象。这时,为了方便设计和访问,直接将A类定义在B类中。就可以了。A类就称为内部类。内部类可以直接访问外部类中的成员。而外部类想要访问内部类,必须要建立内部类的对象。

当内部类定义在外部类中的成员位置上,可以使用一些成员修饰符修饰 private、static。
1:默认修饰符。
直接访问内部类格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象;
Outer.Inner in = new Outer.new Inner();//这种形式很少用。
但是这种应用不多见,因为内部类之所以定义在内部就是为了封装。想要获取内部类对象通常都通过外部类的方法来获取。这样可以对内部类对象进行控制。
2:私有修饰符。
通常内部类被封装,都会被私有化,因为封装性不让其他程序直接访问。
3:静态修饰符。
如果内部类被静态修饰,相当于外部类,会出现访问局限性,只能访问外部类中的静态成员。
注意;如果内部类中定义了静态成员,那么该内部类必须是静态的。

内部类编译后的文件名为:“外部类名$内部类名.java”;

为什么内部类可以直接访问外部类中的成员呢?
那是因为内部中都持有一个外部类的引用。这个是引用是 外部类名.this
内部类可以定义在外部类中的成员位置上,也可以定义在外部类中的局部位置上。当内部类被定义在局部位置上,只能访问局部中被final修饰的局部变量。

匿名内部类(对象):没有名字的内部类。就是内部类的简化形式。一般只用一次就可以用这种形式。匿名内部类其实就是一个匿名子类对象。想要定义匿名内部类:需要前提,内部类必须继承一个类或者实现接口。

匿名内部类的格式:new 父类名&接口名(){ 定义子类成员或者覆盖父类方法 }.方法。

匿名内部类的使用场景:
当函数的参数是接口类型引用时,如果接口中的方法不超过3个。可以通过匿名内部类来完成参数的传递。

其实就是在创建匿名内部类时,该类中的封装的方法不要过多,最好两个或者两个以内。

5 封装

封 装(面向对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离;便于使用;提高重用性;安全性
封装原则:将不需要对外提供的内容都隐藏起来,把属性都隐藏,提供公共方法对其访问。

6 继承

子类中的成员:
1)从父类中继承的可视化(非私有的)的成员(字段、方法),不能继承父类的构造器
2)子类所特有的成员(字段、方法、构造器)
3)重载父类的成员(方法) 多态

好处
1:提高了代码的复用性。
2:让类与类之间产生了关系,提供了另一个特征多态的前提。

重载
父类的由来:其实是由多个类不断向上抽取共性内容而来的。
java中对于继承,java只支持单继承。java虽然不直接支持多继承,但是保留了这种多继承机制,进行改良。

  • 单继承:一个类只能有一个父类。
  • 多继承:一个类可以有多个父类。

为什么不支持多继承呢?
因为当一个类同时继承两个父类时,两个父类中有相同的功能,那么子类对象调用该功能时,运行哪一个呢?因为父类中的方法中存在方法体。
但是java支持多重继承。A继承B B继承C C继承D。
多重继承的出现,就有了继承体系。体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。
所以,一个体系要想被使用,直接查阅该系统中的父类的功能即可知道该体系的基本用法。那么想要使用一个体系时,需要建立对象。建议建立最子类对象,因为最子类不仅可以使用父类中的功能。还可以使用子类特有的一些功能。
简单说:对于一个继承体系的使用,查阅顶层父类中的内容,创建最底层子类的对象。

子父类出现后,类中的成员都有了哪些特点:
1:成员变量。
当子父类中出现一样的属性时,子类类型的对象,调用该属性,值是子类的属性值。
如果想要调用父类中的属性值,需要使用一个关键字:super

  • This:代表是本类类型的对象引用。
  • Super :代表是子类所属的父类中的内存空间引用。

注意:子父类中通常是不会出现同名成员变量的,因为父类中只要定义了,子类就不用在定义了,直接继承过来用就可以了。
2:成员函数。
当子父类中出现了一模一样的方法时,建立子类对象会运行子类中的方法。好像父类中的方法被覆盖掉一样。所以这种情况,是函数的另一个特性:覆盖(复写,重写)
什么时候使用覆盖呢?当一个类的功能内容需要修改时,可以通过覆盖来实现。
3:构造函数。

子类构造函数运行时,先运行了父类的构造函数。为什么呢?
原因:子类的所有构造函数中的第一行,其实都有一条隐身的语句super();
super(): 表示父类的构造函数,并会调用于参数相对应的父类中的构造函数。而super():是在调用父类中空参数的构造函数。

为什么子类对象初始化时,都需要调用父类中的函数?(为什么要在子类构造函数的第一行加入这个super()?)
因为子类继承父类,会继承到父类中的数据,所以必须要看父类是如何对自己的数据进行初始化的。所以子类在进行对象初始化时,先调用父类的构造函数,这就是子类的实例化过程。

注意
子类中所有的构造函数都会默认访问父类中的空参数的构造函数,因为每一个子类构造内第一行都有默认的语句super();

  • 如果父类中没有空参数的构造函数,那么子类的构造函数内,必须通过super语句指定要访问的父类中的构造函数。
  • 如果子类构造函数中用this来指定调用子类自己的构造函数,那么被调用的构造函数也一样会访问父类中的构造函数。

super()和this()是否可以同时出现的构造函数中?
两个语句只能有一个定义在第一行,所以只能出现其中一个。

super()或者this():为什么一定要定义在第一行?
因为super()或者this()都是调用构造函数,构造函数用于初始化,所以初始化的动作要先完成。

继承的细节:
什么时候使用继承呢?
当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。
英文书中,所属关系:” is a “
注意:不要仅仅为了获取其他类中的已有成员进行继承。
所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。

在方法覆盖时,注意两点:
1:子类覆盖父类时,必须要保证,子类方法的权限必须大于等于父类方法权限可以实现继承。否则,编译失败。
2:覆盖时,要么都静态,要么都不静态。 (静态只能覆盖静态,或者被静态覆盖)
继承的一个弊端:打破了封装性。对于一些类,或者类中功能,是需要被继承,或者复写的。
这时如何解决问题呢?介绍一个关键字,final:【后面介绍】。

this

This:代表对象,就是所在函数所属对象的引用。
this到底代表什么呢?
哪个对象调用了this所在的函数,this就代表哪个对象,就是哪个对象的引用。

开发时,什么时候使用this呢?
在定义功能时,如果该功能内部使用到了调用该功能的对象,这时就用this来表示这个对象。
this 还可以用于构造函数间的调用。
调用格式:this(实际参数);

this对象后面跟上 . 调用的是成员属性和成员方法(一般方法);
this对象后面跟上 () 调用的是本类中的对应参数的构造函数

注意:用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。

super

final

final特点:
1:这个关键字是一个修饰符,可以修饰类,方法,变量。
2:被final修饰的类是一个最终类,不可以被继承。

  • final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。

3:被final修饰的方法是一个最终方法,不可以被覆盖。

  • 使用final方法的原因有二:
    第一、把方法锁定,防止任何继承类修改它的意义和实现。
    第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。

4:被final修饰的变量是一个常量,只能赋值一次。

  • final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
    其实这样的原因的就是给一些固定的数据起个阅读性较强的名称。
  • 不加final修饰不是也可以使用吗?那么这个值是一个变量,是可以更改的。加了final,程序更为严谨。常量名称定义时,有规范,所有字母都大写,如果由多个单词组成,中间用 _ 连接。

5:final参数
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

static

static: 关键字,是一个修饰符,用于修饰成员(成员变量和成员函数)。

static 可以修饰的内容
字段:所有对象共享
方法:静态方法不能直接访问非静态的成员,如果需要访问,产生实例来访问;

  • 非静态方法能够直接访问静态成员和非静态成员
  • 静态方法中不能有this、super关键字

块:内存中只执行一次,通常用来初始化静态成员

1、static变量
被static修饰的变量叫静态变量或者类变量
没有被static修饰的变量叫实例变量

  • 对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
  • 对于实例变量,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

2、静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字【因为this代表对象,而静态在时,有可能没有对象,所以this无法使用】,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法【因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员】,因为实例成员与特定的对象关联。
由于static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract

3、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。如果和主函数在同一类中,优先于主函数执行。

特点:
1,想要实现对象中的共性数据的对象共享,可以将这个数据进行静态修饰。
2,被静态修饰的成员,可以直接被类名所调用。也就是说,静态的成员多了一种调用方式。类名.静态方法名/变量名
3,静态随着类的加载而加载,而且优先于对象存在。

弊端:
1,有些数据是对象特有的数据,是不可以被静态修饰的。
因为那样的话,特有数据会变成对象的共享数据。这样对事物的描述就出了问题。所以,在定义静态时,必须要明确,这个数据是否是被对象所共享的。
2,静态方法只能访问静态成员,不可以访问非静态成员。
因为静态方法加载时,优先于对象存在,所以没有办法访问对象中的成员。
3,静态方法中不能使用this,super关键字
因为this代表对象,而静态在时,有可能没有对象,所以this无法使用。
4,主函数是静态的。

什么时候定义静态成员呢?
或者说:定义成员时,到底需不需要被静态修饰呢?
成员分两种:
1,成员变量。(数据共享时静态化)
该成员变量的数据是否是所有对象都一样:
如果是,那么该变量需要被静态修饰,因为是共享的数据。
如果不是,那么就说这是对象的特有数据,要存储到对象中。
2,成员函数。(方法中没有调用特有数据时就定义成静态)
如果判断成员函数是否需要被静态修饰呢?
只要参考,该函数内是否访问了对象中的特有数据:
如果有访问特有数据,那方法不能被静态修饰。
如果没有访问过特有数据,那么这个方法需要被静态修饰。

成员变量和静态变量的区别:
1,成员变量所属于对象,所以也称为实例变量
静态变量所属于类,所以也称为类变量
2,成员变量存在于堆内存中。
静态变量存在于 方法区 中。
3,成员变量随着对象创建而存在,随着对象被回收而消失。
静态变量随着类的加载而存在,随着类的消失而消失。
4,成员变量只能被对象所调用。
静态变量可以被对象调用,也可以被类名调用。
所以,成员变量可以称为对象的特有数据,静态变量称为对象的共享数据。

静态代码块、构造代码块、构造函数同时存在时的执行顺序:静态代码块 —>构造代码块 –> 构造函数;

抽象类

抽象:不具体,看不明白。抽象类表象体现。
在不断抽取过程中,将共性内容中的方法声明抽取,但是方法不一样,没有抽取,这时抽取到的方法,并不具体,需要被指定关键字abstract所标示,声明为抽象方法。
抽象方法所在类一定要标示为抽象类,也就是说该类需要被abstract关键字所修饰。

抽象类的特点:
1:抽象方法只能定义在抽象类中,抽象类和抽象方法必须由abstract关键字修饰(可以描述类和方法,不可以描述变量)。
2:抽象方法只定义方法声明,并不定义方法实现。
3:抽象类不可以被创建对象(实例化)。
4:只有通过子类继承抽象类并覆盖了抽象类中的所有抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

抽象类的细节:
1:**抽象类中是否有构造函数?**有,用于给子类对象进行初始化。
2:抽象类中是否可以定义非抽象方法?
可以。其实,抽象类和一般类没有太大的区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。
所以抽象类和一般类在定义上,都是需要定义属性和行为的。
只不过,比一般类多了一个抽象函数。而且比一般类少了一个创建对象的部分。
3:抽象关键字abstract和哪些不可以共存? final , private , static
4:抽象类中可不可以不定义抽象方法? 可以。抽象方法目的仅仅为了不让该类创建对象。

接口

1:是用关键字interface定义的。

2:接口中包含的成员,最常见的有全局常量、抽象方法。
注意:接口中的成员都有固定的修饰符。(当修饰符没有写时,默认会自动添加)
成员变量:public static final
成员方法:public abstract
interface Inter{
public static final int x = 3;
public abstract void show();
}

3:接口中有抽象方法,说明接口不可以实例化。接口的子类必须实现了接口中所有的抽象方法后,该子类才可以实例化。否则,该子类还是一个抽象类。

4:类与类之间存在着继承关系,类与接口中间存在的是实现关系。
继承用extends ;实现用implements ;

5:接口和类不一样的地方,就是,接口可以被多实现,这就是多继承改良后的结果。java将多继承机制通过多现实来体现。

6:一个类在继承另一个类的同时,还可以实现多个接口。所以接口的出现避免了单继承的局限性。还可以将类进行功能的扩展。

7:其实java中是有多继承的。接口与接口之间存在着继承关系,接口可以多继承接口。
java类是单继承的。classB Extends classA
java接口可以多继承。Interface3 Extends Interface0, Interface1, interface……
不允许类多重继承的主要原因是,如果A同时继承B和C,而b和c同时有一个D方法,A如何决定该继承那一个呢?
但接口不存在这样的问题,接口全都是抽象方法继承谁都无所谓,所以接口可以继承多个接口。

抽象类与接口:

  • 抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
  • 接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。

抽象类和接口的共性:都是不断向上抽取的结果。
抽象类和接口的区别
1:抽象类只能被继承,而且只能单继承。
接口需要被实现,而且可以多实现。
2:抽象类中可以定义非抽象方法,子类可以直接继承使用。
接口中都是抽象方法,需要子类去实现。
3:抽象类使用的是 is a 关系。
接口使用的 like a 关系。
4:抽象类的成员修饰符可以自定义。
接口中的成员修饰符是固定的。全都是public的。

7 多态

概述
动态绑定是指在“执行期间”(而非编译期间)判断所引用的实际对象类型,根据其实际的类型调用其相应的方法。所以实际当中找要调用的方法时是动态的去找的,new的是谁就找谁的方法,这就叫动态绑定。动态绑定帮助我们的程序的可扩展性达到了极致。

体现:父类引用或者接口的引用指向了自己的子类对象。

//例子
class Animal {
    private String name;
    Animal(String name) {
        this.name = name;
    }
    public void enjoy() {
        System.out.println("动物的叫声……");
    }
}

class Cat extends Animal {
    private String eyesColor;
    Cat(String n, String c) {
        super(n);
        eyesColor = c;
    }
    public void enjoy() {
        System.out.println("我养的猫高兴地叫了一声……");
    }
}
class Lady {
    private String name;
    private Animal pet;
    Lady(String name, Animal pet) {//☆ 注意、这里的形参是 Animal
        this.name = name;
        this.pet = pet;
    }
    public void myPetEnjoy() {
        pet.enjoy();
    }
}

public class TestPolymoph {
    public static void main(String args[]) {
        /**
         * 在堆内存里面new了一只蓝猫对象出来,这个蓝猫对象里面包含有一个父类对象Animal。
         */
        Cat c = new Cat("Catname", "blue");
        
        /**
         * 在堆内存里面new出来个小姑娘,名字是l1
         * l1养了一只宠物是c(Cat),
         * 注意:调用Lady类的构造方法时,传递过来的c是当成Animal来传递的,
         * 因此使用c这个引用对象只能访问父类Animal里面的enjoy()方法。
         */
        Lady l1 = new Lady("l1", c);
        
        l1.myPetEnjoy();
    }
}

结果:我养的猫高兴地叫了一声……

多态的好处:提高了程序的扩展性。继承的父类或接口一般是类库中的东西,(如果要修改某个方法的具体实现方式)只有通过子类去覆写要改变的某一个方法,这样在通过将父类的应用指向子类的实例去调用覆写过的方法就行了!

多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。(前期不能使用后期产生的功能,即访问的局限性)

多态的存在有三个必要的条件:
1. 要有继承(两个类之间存在继承关系,子类继承父类)
2. 要有重写(在子类里面重写从父类继承下来的方法)
3. 父类引用指向子类对象
这三个条件一旦满足,当你调用父类里面被重写的方法的时候,实际当中new的是哪个子类对象,就调用子类对象的方法(这个方法是从父类继承下来后重写后的方法)。

判断一个对象是否所属于指定的类型: 对象 instanceof 类型

动态多态和静态多态
动态多态(运行时多态):运行过程中才知道调用那个方法,指的是方法重写
静态多态(编译时多态):编译时就知道所调用的方法,指的是方法重载

六、集合框架

集合框架:用于存储数据的容器。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在于每个容器的内部数据结构不同。
集合容器在不断向上抽取过程中。出现了集合体系。
在使用一个体系时,原则:参阅顶层内容。建立底层对象。
在这里插入图片描述


List

–< java.util >-- List接口:
List 本身是Collection接口的子接口,具备了Collection的所有方法。现在学习List体系特有的共性方法,查阅方法发现List的特有方法都有索引,这是该集合最大的特点。
List有序(元素存入集合的顺序和取出的顺序一致),元素都有索引元素可以重复
|–ArrayList:底层的数据结构是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。
|–LinkedList:底层的数据结构是链表,线程不同步,增删元素的速度非常快。
|–Vector:底层的数据结构就是数组,线程同步的,Vector无论查询和增删都巨慢。

可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。
Vector:是按照原数组的100%延长。


Set

–< java.util >-- Set接口:
数据结构:数据的存储方式;
Set 接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器
|–HashSet:底层数据结构是哈希表,线程是不同步的。无序,高效;

  • HashSet集合保证元素唯一性:通过元素的hashCode方法,和equals方法完成的。
  • 当元素的hashCode值相同时,才继续判断元素的equals是否为true。
    • 如果为true,那么视为相同元素,不存。如果为false,那么存储。
    • 如果hashCode值不同,那么不判断equals,从而提高对象比较的速度。

|–LinkedHashSet有序,hashset的子类。
|–TreeSet:对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树

对于ArrayList集合,判断元素是否存在,或者删除元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。


Map

Map集合:
|–Hashtable:底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。
|–HashMap:底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了Hashtable.
|–TreeMap:底层是二叉树结构,可以对map集合中的键进行指定顺序的排序。

Map集合存储和Collection有着很大不同:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
特点:要保证map集合中键的唯一性

获取map中的所有元素的方法:
原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。
把map集合转成set的方法:

  • Set< V > keySet();
  • Set<Map.Entry<K,V> entrySet();//取的是键和值的映射关系。

Entry就是Map接口中的内部接口;
为什么要定义在map内部呢?entry是访问键值关系的入口,是map的入口,访问的是map中的键值对


取出map集合中所有元素的方式一keySet()方法。
可以将map集合中的键都取出存放到set集合中。对set集合进行迭代。迭代完成,再通过get方法对获取到的键进行值的获取。

	Set keySet = map.keySet();
    Iterator it = keySet.iterator();
    while(it.hasNext()) {
        Object key = it.next();
        Object value = map.get(key);
        System.out.println(key+":"+value);
       }

取出map集合中所有元素的方式二entrySet()方法。

  Set entrySet = map.entrySet();
  Iterator it = entrySet.iterator();
  while(it.hasNext()) {
      Map.Entry me = (Map.Entry)it.next();
      System.out.println(me.getKey()+"::::"+me.getValue());
  }

将非同步集合转成同步集合的方法:
Collections中的 XXX
synchronized XXX(XXX);

List synchronizedList(list);
Map synchronizedMap(map);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<K,V>(m);
}
原理:定义一个类,将集合所有的方法加同一把锁后返回。
List list = Collections.synchronizedList(new ArrayList());
Map<String,String> synmap = Collections.synchronizedMap(map);

Collection 和 Collections的区别:

  • Collections是个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查找、排序、替换、线程安全化(将非同步的集合转换成同步的)等操作。
  • Collection是个java.util下的接口,它是各种集合结构的父接口,继承于它的接口主要有Set和List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。

七、I/O

1 流的分类

在这里插入图片描述
注意:输入流和输出流都是站在程序(内存)的角度上来说

2 结点流与处理流

在这里插入图片描述
节点流类型
在这里插入图片描述
节点流就是一根管道直接插到数据源上面,直接读数据源里面的数据,或者是直接往数据源里面写入数据。典型的节点流是文件流:文件的字节输入流(FileInputStream),文件的字节输出流(FileOutputStream),文件的字符输入流(FileReader),文件的字符输出流(FileWriter)。

处理流类型
在这里插入图片描述
处理流是包在别的流上面的流,相当于是包到别的管道上面的管道。

3 基本流介绍

InputStream
在这里插入图片描述
OutputStream
在这里插入图片描述

Reader
在这里插入图片描述
Writer
在这里插入图片描述

4 结点流介绍

FileInputStream

1 构造方法

  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
2 读取字节数据
读取字节read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
使用字节数组读取read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1

小贴士:

  1. 虽然读取了一个字节,但是会自动提升为int类型。
  2. 流操作完毕后,必须释放系统资源,调用close方法,千万记得。

FileOutputStream

1 构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

2 写出字节数据
写出字节write(int b) 方法,每次可以写出一个字节数据
写出字节数组write(byte[] b),每次可以写出数组中的数据
写出指定长度字节数组write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节

3 数据追加续写

  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

4 写出换行
Windows系统里,换行符号是 \r\n

FileReader

1 构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

2 读取字符数据
读取字符read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取
使用字符数组读取read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1

// 读取文件常用的格式
public class FISRead {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
         FileReader fr = new FileReader("read.txt");
        // 定义变量,保存有效字符个数
        int len ;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf,0,len));
        }
        // 关闭资源
        fr.close();
    }
}

FileWriter

1 构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

2 基本写出数据

写出字符write(int b) 方法,每次可以写出一个字符数据
写出字符数组write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可以写出字符数组中的数据,用法类似FileOutputStream
写出字符串write(String str)write(String str, int off, int len) ,每次可以写出字符串中的数据,更为方便

3 关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close: 先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
//写出数据常用的格式
public class FWWrite {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("fw.txt");     
        String msg = "HELLO WORLD!";
        fw.write(msg); //HELLO WORLD!
        fw.write(msg,6,5);// WORLD
        // 关闭资源
        fw.close();
    }
}

5 处理流介绍

缓冲流(Buffering)

带有缓冲区的流,缓冲区(Buffer)就是内存里面的一小块区域,读写数据时都是先把数据放到这块缓冲区域里面,减少io对硬盘的访问次数,保护我们的硬盘

使用步骤:

public class TestBufferStream {
    public static void main(String args[]) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("D:/java/TestFileInputStream.java");
            // 在FileInputStream节点流的外面套接一层处理流BufferedInputStream
            BufferedInputStream bis = new BufferedInputStream(fis);
            int c = 0;
            System.out.println((char) bis.read());
            System.out.println((char) bis.read());
            bis.mark(100);// 在第100个字符处做一个标记
            for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
                System.out.print((char) c);
            }
            System.out.println();
            bis.reset();// 重新回到原来标记的地方
            for (int i = 0; i <= 10 && (c = bis.read()) != -1; i++) {
                System.out.print((char) c);
            }
            bis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
}
public class TestBufferStream1{
    public static void main(String args[]){
        try{
        BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\java\\dat.txt"));
            //在节点流FileWriter的外面再套一层处理流BufferedWriter
            String s = null;
            for(int i=0;i<100;i++){
                s = String.valueOf(Math.random());//“Math.random()”将会生成一系列介于0~1之间的随机数。
                // static String valueOf(double d)这个valueOf()方法的作用就是把一个double类型的数转换成字符串
                //valueOf()是一个静态方法,所以可以使用“类型.静态方法名”的形式来调用 
                bw.write(s);//把随机数字符串写入到指定文件中
                bw.newLine();//调用newLine()方法使得每写入一个随机数就换行显示
            }
            bw.flush();//调用flush()方法清空缓冲区
            
        BufferedReader br = new BufferedReader(new FileReader("D:/java/dat.txt"));
                //在节点流FileReader的外面再套一层处理流BufferedReader
            while((s = br.readLine())!=null){
                //使用BufferedReader处理流里面提供String readLine()方法读取文件中的数据时是一行一行读取的
                //循环结束的条件就是使用readLine()方法读取数据返回的字符串为空值后则表示已经读取到文件的末尾了。
                System.out.println(s);                
            }
        bw.close();
        br.close();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

转换流

转换流非常的有用,它可以把一个字节流转换成一个字符流,转换流有两种,一种叫InputStreamReader,另一种叫OutputStreamWriter。InputStream是字节流,Reader是字符流,InputStreamReader就是把InputStream转换成Reader。OutputStream是字节流,Writer是字符流,OutputStreamWriter就是把OutputStream转换成Writer。把OutputStream转换成Writer之后就可以一个字符一个字符地通过管道写入数据了,而且还可以写入字符串。我们如果用一个FileOutputStream流往文件里面写东西,得要一个字节一个字节地写进去,但是如果我们在FileOutputStream流上面套上一个字符转换流,那我们就可以一个字符串一个字符串地写进去。

  • InputStreamReader:是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
  • OutputStreamWriter:将内存中字符数据以字节的形式写出到文件是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
public class TestTransform1 {
    public static void main(String args[]) {
        try {
            OutputStreamWriter osw = new OutputStreamWriter(
                    new FileOutputStream("D:/java/char.txt"));
            osw.write("MircosoftsunIBMOracleApplet");// 把字符串写入到指定的文件中去
            System.out.println(osw.getEncoding());// 使用getEncoding()方法取得当前系统的默认字符编码
            osw.close();
            osw = new OutputStreamWriter(new FileOutputStream(
                    "D:\\java\\char.txt", true), "ISO8859_1");
            // 如果在调用FileOutputStream的构造方法时没有加入true,那么新加入的字符串就会替换掉原来写入的字符串,在调用构造方法时指定了字符的编码
            osw.write("MircosoftsunIBMOracleApplet");// 再次向指定的文件写入字符串,新写入的字符串加入到原来字符串的后面
            System.out.println(osw.getEncoding());
            osw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class TestTransform2{
    public static void main(String args[]){
        try{
            InputStreamReader isr = new InputStreamReader(System.in);
            //System.in这里的in是一个标准的输入流,用来接收从键盘输入的数据
            BufferedReader br = new BufferedReader(isr);
            String s = null;
            s = br.readLine();//使用readLine()方法把读取到的一行字符串保存到字符串变量s中去
            while(s != null){
                System.out.println(s.toUpperCase());//把保存在内存s中的字符串打印出来
                s = br.readLine();//在循环体内继续接收从键盘的输入
                if(s.equalsIgnoreCase("exit")){
                    //只要输入exit循环就结束,就会退出
                    break;
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

数据流

package cn.gacl.test;

import java.io.*;
public class TestDataStream{
    public static void main(String args[]){
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //在调用构造方法时,首先会在内存里面创建一个ByteArray字节数组
        DataOutputStream dos = new DataOutputStream(baos);
        //在输出流的外面套上一层数据流,用来处理int,double类型的数
        try{
            dos.writeDouble(Math.random());//把产生的随机数直接写入到字节数组ByteArray中
            dos.writeBoolean(true);//布尔类型的数据在内存中就只占一个字节
        	ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            System.out.println(bais.available());
            DataInputStream dis = new DataInputStream(bais);
            System.out.println(dis.readDouble());//先写进去的就先读出来,调用readDouble()方法读取出写入的随机数
            System.out.println(dis.readBoolean());//后写进去的就后读出来,这里面的读取顺序不能更改位置,否则会打印出不正确的结果
            dos.close();
            bais.close();
        }catch(Exception e){
                e.printStackTrace();
            }
    }
}

通过bais这个流往外读取数据的时候,是一个字节一个字节地往外读取的,因此读出来的数据无法判断是字符串还是bool类型的值,因此要在它的外面再套一个流,通过dataInputStream把读出来的数据转换就可以判断了。注意了:读取数据的时候是先写进去的就先读出来,因此读ByteArray字节数组数据的顺序应该是先把占8个字节的double类型的数读出来,然后再读那个只占一个字节的boolean类型的数,因为double类型的数是先写进数组里面的,读的时候也要先读它。这就是所谓的先写的要先读。如果先读Boolean类型的那个数,那么读出来的情况可能就是把double类型数的8个字节里面的一个字节读了出来

打印流

/*这个小程序是重新设置打印输出的窗口,
 * 把默认在命令行窗口输出打印内容设置成其他指定的打印显示窗口
 */
public class TestPrintStream{
    public static void main(String args[]){
        PrintStream ps = null;
        try{
                FileOutputStream fos = new FileOutputStream("D:/java/log.txt");
                ps = new PrintStream(fos);//在输出流的外面套接一层打印流,用来控制打印输出
                if(ps != null){
                    System.setOut(ps);//这里调用setOut()方法改变了输出窗口,以前写System.out.print()默认的输出窗口就是命令行窗口.
                    //但现在使用System.setOut(ps)将打印输出窗口改成了由ps指定的文件里面,通过这样设置以后,打印输出时都会在指定的文件内打印输出
                    //在这里将打印输出窗口设置到了log.txt这个文件里面,所以打印出来的内容会在log.txt这个文件里面看到
                }
            for(char c=0;c<=60000;c++){
                    System.out.print(c+"\t");//把世界各国的文字打印到log.txt这个文件中去
                }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

对象流

public class TestObjectIo {
    public static void main(String args[]) {
        T t = new T();
        t.k = 8;// 把k的值修改为8
        try {
            FileOutputStream fos = new FileOutputStream(
                    "D:/java/TestObjectIo.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            // ObjectOutputStream流专门用来处理Object的,在fos流的外面套接ObjectOutputStream流就可以直接把一个Object写进去
            oos.writeObject(t);// 直接把一个t对象写入到指定的文件里面
            oos.flush();
            oos.close();
            FileInputStream fis = new FileInputStream(
                    "D:/java/TestObjectIo.txt");
            ObjectInputStream ois = new ObjectInputStream(fis);
            // ObjectInputStream专门用来读一个Object的
            T tRead = (T) ois.readObject();
            // 直接把文件里面的内容全部读取出来然后分解成一个Object对象,并使用强制转换成指定类型T
            System.out.print(tRead.i + "\t" + tRead.j + "\t" + tRead.d + "\t"
                    + tRead.k);
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/*
 * 凡是要将一个类的对象序列化成一个字节流就必须实现Serializable接口
 * Serializable接口中没有定义方法,Serializable接口是一个标记性接口,用来给类作标记,只是起到一个标记作用。
 * 这个标记是给编译器看的,编译器看到这个标记之后就可以知道这个类可以被序列化 如果想把某个类的对象序列化,就必须得实现Serializable接口
 */
class T implements Serializable {
    // Serializable的意思是可以被序列化的
    int i = 10;
    int j = 9;
    double d = 2.3;
    int k = 15;
    // transient int k = 15;
    // 在声明变量时如果加上transient关键字,那么这个变量就会被当作是透明的,即不存在。
}

直接实现Serializable接口的类是JDK自动把这个类的对象序列化,而如果实现public interface Externalizable extends Serializable的类则可以自己控制对象的序列化,建议能让JDK自己控制序列化的就不要让自己去控制

6 IO流总结

在这里插入图片描述

八、lambda表达式

Java 8(JDK 1.8)中,加入了Lambda表达式

Lambda表达式的标准格式为:

(参数类型 参数名称) -> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • ->是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。
        Arrays.sort(array, (Person a, Person b) -> {
              return a.getAge() - b.getAge();
        });

Lambda的使用前提
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。//可以有多个非抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  2. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

九、异常

异常的分类

在这里插入图片描述
Throwable:可抛出的。
|–Error:错误,一般情况下,不编写针对性的代码进行处理,通常是jvm发生的,需要对程序进行修正。
|–Exception:异常,可以有针对性的处理方式
这个体系中的所有类和对象都具备一个独有的特点;就是可抛性。
可抛性的体现:就是这个体系中的类和对象都可以被throws和throw两个关键字所操作。

异常的处理

Java异常处理的五个关键字:try、catch、finally、throw、throws

throw与throws区别:
throws 是用来声明一个方法可能抛出的所有异常信息,而throw则是指抛出的一个具体的异常类型。此外throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理。

throw用于抛出异常对象,后面跟的是异常对象;throw用在函数内。
throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws用在函数上。

throws格式:方法名(参数)throws 异常类1,异常类2,…
throw:就是自己进行异常处理,处理的时候有两种方式,要么自己捕获异常(也就是try catch进行捕捉),要么声明抛出一个异常(就是throws 异常~~)。

处理方式有两种:1、捕捉;2、抛出。

定义异常处理时,什么时候定义try,什么时候定义throws呢?
功能内部如果出现异常,如果内部可以处理,就用try;
如果功能内部处理不了,就必须声明出来,让调用者处理。使用throws抛出,交给调用者处理。谁调用了这个功能谁就是调用者;

自定义异常的步骤:
1:定义一个子类继承Exception或RuntimeException,让该类具备可抛性(既可以使用throw和throws去调用此类)。
2:通过throw 或者throws进行操作。
异常的转换思想:当出现的异常是调用者处理不了的,就需要将此异常转换为一个调用者可以处理的异常抛出。

注意:
如果父类或者接口中的方法没有抛出过异常,那么子类是不可以抛出异常的,如果子类的覆盖的方法中出现了异常,只能try不能throws。
如果这个异常子类无法处理,已经影响了子类方法的具体运算,这时可以在子类方法中,通过throw抛出RuntimeException异常或者其子类,这样,子类的方法上是不需要throws声明的。

十、泛型

泛型:jdk1.5版本以后出现的一个安全机制。表现格式:< >
好处
1:将运行时期的问题ClassCastException问题转换成了编译失败,体现在编译时期,程序员就可以解决问题。
2:避免了强制转换的麻烦。

泛型中的通配符:可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。

十一、反射

反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。

反射的好处:大大的增强了程序的扩展性。

反射的基本步骤:
1、获得Class对象,就是获取到指定的名称的字节码文件对象。
2、实例化对象,获得类的属性、方法或构造函数。
3、访问属性、调用方法、调用构造函数创建对象。

获取这个Class对象,有三种方式:
1:通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。
2:每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。
前两种方式不利于程序的扩展,因为都需要在程序使用具体的类来完成。
3:使用的Class类中的方法,静态的forName方法。
指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。

// 1. 根据给定的类名来获得 用于类加载
String classname = "cn.itcast.reflect.Person";// 来自配置文件
Class clazz = Class.forName(classname);// 此对象代表Person.class
// 2. 如果拿到了对象,不知道是什么类型 用于获得对象的类型
Object obj = new Person();
Class clazz1 = obj.getClass();// 获得对象具体的类型
// 3. 如果是明确地获得某个类的Class对象 主要用于传参
Class clazz2 = Person.class;    

反射的用法:
1)、需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
Class.forName(classname) 用于做类加载
obj.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型,传参用
2)、反射类的成员方法:

Class clazz = Person.class;
Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();

3)、反射类的构造函数:

Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)

4)、反射类的属性:

Field field = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);

获取了字节码文件对象后,最终都需要创建指定类的对象:

创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):
1,调用空参数的构造函数:使用了Class类中的newInstance()方法
2,调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。
综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。

通过反射创建对象

	// 如何生成获取到字节码文件对象的实例对象。
     Class clazz = Class.forName("cn.itcast.bean.Person");//类加载
 	// 直接获得指定的类型
     clazz = Person.class;
     // 根据对象获得类型
     Object obj = new Person("zhangsan", 19);
     clazz = obj.getClass();
     /*该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。
     当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();*/
     Object obj = clazz.newInstance();


 public static void method_2() throws Exception {
     Class clazz = Class.forName("cn.itcast.bean.Person");
     //既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
     //获取一个带参数的构造器。
     Constructor constructor = clazz.getConstructor(String.class,int.class);
     //想要对对象进行初始化,使用构造器的方法newInstance();
     Object obj = constructor.newInstance("zhagnsan",30);
     
     //获取所有构造器。
     Constructor[] constructors = clazz.getConstructors();//只包含公共的
     constructors = clazz.getDeclaredConstructors();//包含私有的
     for(Constructor con : constructors) {
         System.out.println(con);
     }
 }

反射指定类中的方法:

  //获取类中所有的方法。
  public static void method_1() throws Exception {
      Class clazz = Class.forName("cn.itcast.bean.Person");
      Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
      methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
      for(Method method : methods) {
          System.out.println(method);
      }
  }
  //获取指定方法;
  public static void method_2() throws Exception {
      Class clazz = Class.forName("cn.itcast.bean.Person");
      //获取指定名称的方法。
      Method method = clazz.getMethod("show", int.class,String.class);
      //想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
      Object obj = clazz.newInstance();
      method.invoke(obj, 39,"hehehe");//执行一个方法
  }
  //想要运行私有方法。
  public static void method_3() throws Exception {
      Class clazz = Class.forName("cn.itcast.bean.Person");
      //想要获取私有方法。必须用getDeclearMethod();
      Method method = clazz.getDeclaredMethod("method", null);
      // 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
      method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
  }
  //反射静态方法。
  public static void method_4() throws Exception {
      Class clazz = Class.forName("cn.itcast.bean.Person");
      Method method = clazz.getMethod("function",null);
      method.invoke(null,null);
  }

十二、并发、多线程

返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread-编号定义的。编号从0开始。
线程要运行的代码都统一存放在了run方法中。
线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
start方法: 1)启动了线程; 2)让jvm调用了run方法。

线程的启动

Thread类中run()和start()方法的区别:
start():用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
run():run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从CPU中申请另一个线程空间来执行 run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那是 在当前线程中执行,run()方法执行完成后继续执行下面的代码.而调用start()方法后,run()方法的代码会和当前线程并发(单CPU)或并行 (多CPU)执行。所以请记住一句话:调用线程对象的run方法不会产生一个新的线程,虽然可以达到相同的执行结果,但执行过程和执行效率不同

线程的创建

创建线程的第一种方式:继承Thread ,由子类复写run方法。
步骤
1,定义类继承Thread类;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,通过创建Thread类的子类对象,创建线程对象;
4,调用线程的start方法,开启线程,并执行run方法。

线程状态:
被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()

创建线程的第二种方式:实现一个接口Runnable。
步骤:
1,定义类实现Runnable接口。
2,覆盖接口中的run方法(用于封装线程要运行的代码)。
3,通过Thread类创建线程对象;
4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。

为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。因为实现Runnable接口可以避免单继承的局限性。
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。

所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。

同步

synchronized关键字
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用.

public class Thread1 implements Runnable {
	public void run() {
		synchronized(this) {
		for (int i = 0; i < 5; i++) {
		System.out.println(Thread.currentThread().getName()+"synchronized loop " + i);
			}
		}
	}
}

☆ synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。
1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:

synchronized(syncObject) {
//允许访问控制的代码
}

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

同步
好处:解决了线程安全问题。Synchronized
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。
同步的第二种表现形式: //对共享资源的方法定义同步
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步函数是用的哪个锁呢? //synchronized(this)用以定义需要进行同步的某一部分代码块。通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。This.方法名

当同步函数被static修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。
这个对象就是 类名.class

同步代码块和同步函数的区别?

  • 同步代码块使用的锁可以是任意对象。
  • 同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
  • 在一个类中只有一个同步的话,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

★考点问题:请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。
//懒汉式:延迟加载方式。
当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。
为了效率问题,通过双重判断的形式解决。

class Single{
    private static Single s = null;
    private Single(){}
    public static Single getInstance(){ //锁是谁?字节码文件对象;
        if(s == null){
            synchronized(Single.class){
                if(s == null)
                    s = new Single();
            }
        }
        return s;
    }
}

等待唤醒机制
涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
notifyAll: 唤醒的是线程池中的所有线程。
注意
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

wait 和 sleep区别: 分析这两个方法:从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
sleep:线程会释放执行权,但不是不释放锁。

线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。
停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。

☆ **怎么结束run方法?**一般run方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

Thread 的方法
---------< java.lang.Thread >----------
interrupt():中断线程。
setPriority(int newPriority):更改线程的优先级。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

public class A{
	public static void main(String args[]){
		B = new B();// B extends Thread
		b.start();
		b.join();
		......
	}
}

Lock
LOCK的出现替代了同步:lock.lock();………lock.unlock();
Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。

在之前的版本中使用Object类中waitnotifynotifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。

< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();

class BoundedBuffer {
	final Lock lock = new ReentrantLock();
	final Condition notFull = lock.newCondition();
	final Condition notEmpty = lock.newCondition();
	final Object[] items = new Object[100];
	int putptr, takeptr, count;

	public void put(Object x) throws InterruptedException {
		lock.lock();
		try {
			while (count == items.length)
				notFull.await();//栈满时阻塞
			items[putptr] = x;
			if (++putptr == items.length) 
				putptr = 0;
			++count;
			notEmpty.signal();//解其他线程的 notEmpty.await() 锁
		}finally {
			lock.unlock();
		}
	}
	
	public Object take() throws InterruptedException {
		lock.lock();
		try {	
			while (count == 0)		
				notEmpty.await();//栈空时阻塞		
			Object x = items[takeptr];		
			if (++takeptr == items.length) takeptr = 0;		
			--count;		
			notFull.signal();		
			return x;	
		}finally {
			lock.unlock();	
		}
	}
}

十三、设计模式

软件设计模式

设计模式:解决问题最行之有效的思想。是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
java中有23种设计模式:

单例设计模式:

解决的问题:保证一个类在内存中的对象唯一性。
比如:多程序读取一个配置文件时,建议配置文件封装成对象。会方便操作其中数据,又要保证多个程序读到的是同一个配置文件对象,就需要该配置文件对象在内存中是唯一的。
Runtime()方法就是单例设计模式进行设计的。
如何保证对象唯一性呢?
思想
1,不让其他程序创建该类对象。
2,在本类中创建一个本类对象。
3,对外提供方法,让其他程序获取这个对象。
步骤
1,因为创建对象都需要构造函数初始化,只要将本类中的构造函数私有化,其他程序就无法再创建该类对象;
2,就在类中创建一个本类的对象;
3,定义一个方法,返回该对象,让其他程序可以通过方法就得到本类对象。(作用:可控)
代码体现
1,私有化构造函数;
2,创建私有并静态的本类对象;
3,定义公有并静态的方法,返回该对象。

//饿汉式
class Single{
	private Single(){} //私有化构造函数。
	private static Single s = new Single(); //创建私有并静态的本类对象。
	public static Single getInstance(){ //定义公有并静态的方法,返回该对象。
	return s;
}
}

//懒汉式:延迟加载方式。
class Single2{
	private Single2(){}
	private static Single2 s = null;
	public static Single2 getInstance(){
		if(s==null)
		s = new Single2();
	return s;
	}
}

模板方法设计模式:

解决的问题:当功能内部一部分实现时确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

abstract class GetTime{
	public final void getTime(){ //此功能如果不需要复写,可加final限定
	long start = System.currentTimeMillis();
	code(); //不确定的功能部分,提取出来,通过抽象方法实现
	long end = System.currentTimeMillis();
	System.out.println("毫秒是:"+(end-start));
	}
	public abstract void code(); //抽象不确定的功能,让子类复写实现
}
class SubDemo extends GetTime{
	public void code(){ <font color=red >//子类复写功能方法</font>
		for(int y=0; y<1000; y++){
			System.out.println("y");
		}
	}
} 

十四、其他

生成Java帮助文档:命令格式:javadoc –d 文件夹名 –auther –version *.java

序列化和反序列化

  1. 序列化与反序列化
    把对象转换为字节序列的过程称为对象的序列化。
    把字节序列恢复为对象的过程称为对象的反序列化。
  2. 使用
    写出数据到文件中 ObjectOutputStream 方法: writeObject
    从文件读取数据 ObjectInputStream 方法:readObject
  3. 显式地定义serialVersionUID有两种用途:
    1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
    2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。

若序列化对象中没有指定serialVersionUID,那么在序列化之后。若在类中添加新的字段,然后用该修改后的类进行反序列化,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值