Java SE学习笔记
类变量
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
…
下面来看一段代码
class Child{
private String name;
public static int count=0; //定义一个变量count,是一个类变量(静态变量),被Child类的所有对象实例共享
public Child(String name) {
this.name = name;
}
public void join(){
System.out.println(name+" 加入了游戏..");
count++;
}
}
静态域因jdk
版本不同,可能存放在堆里,也有可能在方法区里
但可以肯定的是,静态变量被所有对象共享,而且在类加载的时候就生成了(new的时候加载一次,且只加载一次)
关于上面代码里的count
变量,也就是static
变量
在jdk8
以前,会认为是在这个方法区里面,但jdk8
以后静态域放在了堆里面
当类被加载的时候,会在堆里生成一个class
对象,而static
变量就被保存在class
实例的尾部,所以认为静态变量在堆里面
这里顺便提一句,局部变量在使用完之后内存会被释放,但静态变量会保留内存,直到程序结束
访问类变量
- 类名.类变量名
- 对象名.类变量名
这里更推荐使用前者,看起来更规范一些
而实例变量不能通过前者访问
因为类变量随着类的加载而创建,所以没有创建对象实例也可访问类变量
因此,类变量的生命周期是随类的加载开始,随着类消亡而销毁
同时,类变量的访问也要遵守修饰符的访问权限,和普通属性是一样的
什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量
例如:定义学生类,统计所有学生共交多少钱
类变量与实例变量(普通变量)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
加上static
的变量称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
类变量就先记录这么多吧…
类方法
类方法也叫静态方法
当方法使用了static
修饰后,该方法就是静态方法,静态方法就可以访问静态属性/变量
静态方法不能调用非静态变量
形式如下:
- 访问修饰符 static 数据返回类型 方法名(){}
- static 访问修饰符 数据返回类型 方法(){}
这里也是为了规范,推荐使用前者
类方法的调用
- 类名.类方法名
- 对象名.类方法名
跟前面的类变量一样,要满足访问修饰符的访问权限和范围
使用场景
我们可以发现在工具类中提供的方法基本都是静态方法
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
在开放中往往将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等…
如果我们希望不创建实例,也可以调用某个方法,这时候静态方法就是一个很好的使用场景
注意事项
- 类方法也和类变量一致,随着类的加载而加载,将结构信息存储在方法区
- 类方法中无
this
的参数,而普通方法中隐含着this
的参数 - 类方法可以通过类名调用,也可以通过对象名调用,不能通过类名调用
- 类方法中不允许使用和对象有关的关键字,比如
this
和super
。但普通方法可以使用 - 类方法(静态方法)中只能访问静态变量和静态方法,但普通方法即可以访问普通变量和方法,也可以访问静态变量和方法(但必须遵守访问权限)
理解main方法语法
深入理解main方法
解释main
方法的形式
public static void main(String[] args) {}
main
方法时虚拟机调用的- Java虚拟机需要调用类的
main()
方法,所以该方法的访问权限必须是public
- Java虚拟机在执行
main()
方法时不必创建对象,所以该方法必须是static
- 该方法接收
String
类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数 - main的形参,就是把
Java 运行的类名 参数1 参数2 参数3
中最后面的参数形成一个数组带进去,而且可以在主方法里再取出来
同上面的类方法,在main()
方法中,我们可以直接调用main()
方法所在类的静态方法或静态属性,但是,不能直接访问该类的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
main动态传值
刚刚上面讲的Java 运行的类名 参数1 参数2 参数3
是在命令行中的传值方法
下面我们用下面这段代码来说一下idea里怎么进行传递参数
public class Main {
public static void main(String[] args) {
for (int i=0;i< args.length;i++){
System.out.println("args["+i+"]="+args[i]);
}
}
}
我们在idea右上角选择Edit Configurations
在Program arguments
里写入参数即可
如图,这里我们传入杭州 宁波 温州
看看效果
运行后可以看到我们成功传入了参数
main方法就到这…
代码块
基本介绍
代码块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}
包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法
[修饰符]{
代码
};
语法说明:
- 修饰符可选,要写的话,也只能写
static
- 代码块分为两类,使用
static
修饰的叫静态代码块,没有static
修饰的,叫普通代码块 - 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
;
号可以写上,也可以省略
代码块的好处:
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作,顺序优先于构造器
- 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
细节(重点)
static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
类什么时候被加载
①创建对象实例时(new)
②创建子类对象实例,父类也会被加载
③使用类的静态成员时(静态属性、静态方法)
普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行
静态代码块和静态属性初始化的优先级一样,按他们定义的顺序调用,普通代码块和普通属性初始化的优先级也一样,但是会先调用静态,其次按他们定义的顺序调用,最后调用构造器,总结一下就是静态>普通>构造器
构造器的最前面其实隐含了super()和调用普通代码块(先父类按顺序,再子类按顺序)
调用顺序
①父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
②子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
③父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
④父类的构造方法
⑤子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
⑥子类的构造方法
静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
单例设计模式
什么是设计模式
- 静态方法和属性的经典使用
- 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式
什么是单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种方式:
①饿汉式
②懒汉式
单例模式应用实例-饿汉式
实现步骤
- 构造器私有化==>防止直接
new
- 类的内部创建静态对象
- 向外暴露一个静态的公共方法
getInstance
- 代码实现
这里解释一下,之所以要将getInstance
方法设置为静态方法,是因为避免去new一个对象,那又因为要让getInstance
这个静态方法能够使用到类内部创建的对象,所以需要将对象设置为静态的
下面看一下具体的代码
public class SingleTon01 {
public static void main(String[] args) {
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
if (instance==instance2){ //可以看到两次获取到的都是同一对象
System.out.println("true");
}
}
}
class GirlFriend{
private String name;
private static GirlFriend tank=new GirlFriend("Tank"); //类的内部创建静态对象
private GirlFriend(String name) { //构造器私有化
this.name = name;
}
public static GirlFriend getInstance(){ //向外暴露一个静态的公共方法
return tank;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
题外话
为什么叫饿汉式?
因为不管你有没有使用这个对象,它都已经帮你创建好了一个对象,所以叫饿汉式…相反,不去用它,它就不会创建,被称为懒汉式…
单例模式应用实例-懒汉式
饿汉式不使用对象却也会创建对象,造成了一种资源的浪费,而懒汉式就解决了这一个问题
实现步骤
- 仍然构造器私有化
- 定义一个
static
静态属性对象 - 提供一个
public
的static
方法,可以返回一个Cat对象 - 懒汉式,只有当用户使用
getInstance
时,才返回cat对象,从而再次调用时,会返回上次创建的Cat对象
从而保证了单例
public class SingleTon02 {
public static void main(String[] args) {
System.out.println(Cat.n1);
Cat instance=Cat.getInstance();
System.out.println(instance);
Cat instance2=Cat.getInstance();
System.out.println(instance);
}
}
class Cat{
private String name;
public static int n1=999;
private static Cat cat;
private Cat(String name) {
System.out.println("构造器被调用...");
this.name = name;
}
public static Cat getInstance(){
if (cat==null){ //如果对象还未创建,则创建一个
cat=new Cat("小可爱");
}
return cat;
}
}
总结单例设计模式
饿汉式VS懒汉式
- 最主要的区别就是创建的时机不同,饿汉式在类加载就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题(后续介绍)
- 饿汉式存在浪费资源的可能
- 在
javaSE
标准类中,java.lang.Runtime
就是经典的单例模式
final关键字
基本介绍
final可以修饰类、属性、方法和局部变量
使用场景
当不希望类被继承时,可以用
final
修饰final class A{} //在不想被继承的类前加final class B extends A{} //此处会报错,无法继承A类
当不希望父类的某个方法被子类覆盖/重写时,可以用
final
关键字修饰具体格式为
访问修饰符 final 返回类型 方法名
class C { public final void hi(){} //这里使用final修饰方法 } class D extends C{ @Override public void hi() { //此处会报错,只能继承但不能重写C类的hi方法 System.out.println("重写C类的hi方法"); } }
当不希望类的某个属性的值被修改,可以使用
final
修饰public class Final01 { public static void main(String[] args) { E e = new E(); e.n1=888; //报错,无法修改 } } class E{ public final int n1=999; //这里用final修饰变量 }
当不希望某个局部变量被修改,可以用
final
修饰class F{ public void cry(){ final double Num=0.01; //finall修饰局部变量 Num=0.9; //报错,无法修改 System.out.println("Num= "+Num); } }
注意事项
final
修饰的属性又叫常量,一般用XX_XX_XX
来命名final
修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一①定义时:如
public final double TAX_RATE=0.08;
②在构造器中
③在代码块中
public final double TAX_RATE=0.01; //第一种 public final double TAX_RATE2; //第二种 public final double TAX_RATE3; //第三种 public AA(){ //第二种:在构造器中赋初值 TAX_RATE2=1.1; } { //第三种:在代码块中赋初值 TAX_RATE3=2.2; }
如果
final
修饰的属性是静态的,则初始化的位置只能是①定义时
②在静态代码块,不能再构造器中赋值
public static final double TAX_RATE=0.01; public static final double TAX_RATE2; //报错 public static final double TAX_RATE3; public BB(){ TAX_RATE2=1.1; //构造器不能赋值(静态) } static { TAX_RATE3=2.2; //静态代码块可以赋值 }
final
类不能继承,但是可以实例化对象public class FinalDetail01 { public static void main(String[] args) { CC cc = new CC(); //实例化对象不会报错 } } final class CC{} //final类
如果类不是final类,但是含有
final
方法,则该方法虽然不能重写,但是可以被继承public class FinalDetail01 { public static void main(String[] args) { new EE().cal(); //可以正常调用 } } class DD{ public final void cal(){ //此处含有final方法 System.out.println("cal()方法"); } } class EE extends DD{} //可以继承DD
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法
final不能修饰构造方法(即构造器)
final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理
public static void main(String[] args) { System.out.println(BBB.num); //输出999和BBB被执行 } class BBB{ public static int num=999; static{ System.out.println("BBB 的静态代码块被执行"); } }
public static void main(String[] args) { System.out.println(BBB.num); //仅输出999 } class BBB{ public final static int num=999; static{ System.out.println("BBB 的静态代码块被执行"); //此句不输出 } }
包装类(Integer,Double,Float,Boolean等都是final),String也是final类
抽象类
引入抽象类
当父类的某些方法,需要声明,但是又不确定如何实现时,可以用abstract
关键字来修饰方法,将其声明为抽象方法,那么这个类就是抽象类
具体写法
这里要注意,如果要将方法设为抽象方法,那么需要在类的前面也加上abstract
,否则会报错,同时抽象方法没有方法体,否则会报错
具体格式为访问修饰符 abstract 返回类型 方法名(参数列表);
abstract class Animal{ //类的前面加上abstract
private String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat(); //抽象方法
}
而且,抽象类一般来说会被继承,由其子类来实现抽象方法
使用细节
抽象类不能被实例化
public class Abstract02 { public static void main(String[] args) { new A(); //此处报错,不能实例化抽象类 } } abstract class A{}
抽象类不一定要包含
abstract
方法,换句话说,抽象类可以没有abstract
方法,还可以有实现的方法abstract class A{} //用上面的代码来举例,此处并不会报错
abstract class A{ public void hi(){ System.out.println("hi"); } }
一旦类包含了
abstract
方法,则这个类必须声明为abstract
class B{ public abstract void hi(); //报错,因为包含了抽象方法,所以必须是抽象类 }
abstract
只能修饰类和方法,不能修饰属性和其他class C{ public abstract int a; //报错,abstract只能修饰类和方法 }
抽象类可以有任意成员【抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等等
抽象方法不能有主体,即不能实现(也就是开头所说的不能有方法体)
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为
abstract
类abstract class E{ public abstract void hi(); } class F extends E{} //这里继承了E抽象类,但没有实现所有的抽象方法,所以会报错
abstract class E{ public abstract void hi(); } abstract class F extends E{} //那么我们在子类前加上abstract就不会报错
abstract class E{ public abstract void hi(); } class G extends E{ @Override public void hi() {} //所谓实现方法,就是要有方法体(空也可以) }
抽象方法不能用private、final、static来修饰,因为这些关键字都是和重写相违背的
抽象模板设计
首先我们来看下面一段代码(分别在三个文件中)
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.job();
BB bb = new BB();
bb.job();
}
}
public class AA {
public void job(){
long start=System.currentTimeMillis();
long num=0;
for (long i=1;i<=800000;i++){
num+=i;
}
long end=System.currentTimeMillis();
System.out.println("执行时间:"+(end-start));
}
}
public class BB {
public void job(){
long start=System.currentTimeMillis();
long num=0;
for (long i=1;i<=80000;i++){
num*=i;
}
long end=System.currentTimeMillis();
System.out.println("执行时间:"+(end-start));
}
}
这里提一句,currentTimeMillis()
可以以毫秒为单位获取系统当前的时间,返回long
数据类型的数据
我们发现,AA类和BB类有相当一部分是重复的,下面进行改进,体会一下抽象模板设计
public class TestTemplate {
public static void main(String[] args) {
AA aa = new AA();
aa.calculateTime();
BB bb = new BB();
bb.calculateTime();
}
}
abstract public class Template {
public abstract void job();
public void calculateTime(){
long start=System.currentTimeMillis();
job(); //动态机制绑定到AA或BB的job执行
long end=System.currentTimeMillis();
System.out.println("任务执行时间:"+(end-start));
}
}
public class AA extends Template{
public void job(){ //重写抽象类里的job
long num=0;
for (long i=1;i<=800000;i++){
num+=i;
}
}
}
public class BB extends Template {
public void job(){ ////重写抽象类里的job
long num=0;
for (long i=1;i<=80000;i++){
num*=i;
}
}
}
从上面的实践可以看出来抽象类对代码的复用性和简洁性还是有很大帮助的…
接口
快速入门
看下面的代码模拟一下现实中的USB
接口…
public class Interface01 {
public static void main(String[] args) {
Phone phone = new Phone();
Camera camera = new Camera();
Computer computer = new Computer();
computer.work(phone); //把手机接入到计算机
System.out.println("===============");
computer.work(camera); //把相机接入到计算机
}
}
public interface UsbInterface { //接口中abstract和public可以省略
public void start();
public void stop();
}
public class Phone implements UsbInterface{ //Phone类实现了UsbInterface
@Override
public void start() { //实现UsbInterface接口规定/声明的方法
System.out.println("手机开始工作...");
}
@Override
public void stop() { //实现UsbInterface接口规定/声明的方法
System.out.println("手机停止工作...");
}
}
public class Camera implements UsbInterface{ //Camera类实现了UsbInterface
@Override
public void start() {
System.out.println("相机开始工作了...");
}
@Override
public void stop() {
System.out.println("相机停止工作了...");
}
}
public class Computer {
public void work(UsbInterface usbInterface){ //计算机工作
usbInterface.start();
usbInterface.stop();
}
}
Phone
类实现了UsbInterface
,即Phone
类需要实现UsbInterface
接口规定/声明的方法
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。语法:
interface 接口名{
//属性
//方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
class 类名 implements 接口{
//自己属性
//自己方法
//必须实现接口的抽象方法
}
注意:在JDK7.0
前,接口里的所有方法都没有方法体,即都是抽象方法。JDK8.0
后接口可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
默认实现方法,需要使用default
关键字修饰,如下
default public void ok(){
System.out.println("OK");
}
注意事项
接口不能被实例化
接口中所有的方法是public
方法,接口中抽象方法,可以不用abstract
修饰
一个普通类实现接口,就必须将该接口的所有方法都实现
这里有一个快捷键,在下图报错的位置,使用Alt+Enter
,选择Implement methods
即可快速导入接口中所有要实现的方法
抽象类实现接口,可以不用实现接口的方法
interface IA{
void say();
void hi();
}
abstract class Tiger implements IA{} //不会报错,抽象类可以不实现
一个类同时可以实现多个接口
interface IB{
void hi();
}
interface IC{
void say();
}
class Pig implements IB,IC{ //Pig类实现了IB和IC两个接口
public void hi() {}
public void say() {}
}
接口中的属性,只能是final
的,而且是public static final
修饰符
接口中属性的访问形式:接口名.属性名
接口不能继承其他的类,但是可以继承多个别的接口
interface IB{
void hi();
}
interface IC{
void say();
}
class Pig implements IB,IC{ //同时实现了多个接口,用逗号分隔
public void hi(){}
public void say(){}
}
接口的修饰符只能是public和默认,这点和类的修饰符是一样的
接口和继承的区别
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法
子类继承了父类,就自动的拥有父类的功能,如果子类需要扩展功能,可以通过实现接口的方式扩展,可以理解实现接口是对 单继承机制的一种补充
继承是满足is-a
的关系,而接口只需满足like-a
的关系
接口在一定程度上实现了代码解耦,即:接口规范性+动态绑定机制
接口的多态特性
多态参数,在前面的代码里UsbInterface usb
,即可以接收手机对象,又可以接收相机对象,体现了借口多态,且接口类型的变量可以指向实现了接口的对象实例
多态数组,在Usb数组中,存放Phone和Camera对象,
public class InterfacePolyArr {
public static void main(String[] args) {
Usb[] usb=new Usb[2];
usb[0]=new Phone_();
usb[1]=new Camera_(); //可以存放不同的对象
for (int i=0;i< usb.length;i++){
usb[i].work(); //动态绑定机制
if (usb[i] instanceof Phone_){ //判断运行类型是否一致
((Phone_) usb[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb{
public void call(){
System.out.println("手机可以打电话...");
}
public void work() {
System.out.println("手机工作中...");
}
}
class Camera_ implements Usb{
public void work() {
System.out.println("相机工作中...");
}
}
多态传递,用下面的例子来说,如果IG
继承了IH
接口,而Teacher
类实现了IG
接口,那么,实际上就相当于Teacher
类也实现了IH
接口
public class InterfacePolyPass {
public static void main(String[] args) {
IG ig=new Teacher();
IH ih=new Teacher();
}
}
interface IH{}
interface IG extends IH{}
class Teacher implements IG{}
内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套其他类的类称为外部类,内部类最大的特点就是可以直接访问私有属性,并且可以提现类与类之间的包含关系
基本语法:
class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}
内部类的分类
定义在外部类局部位置上(比如方法内):
- 局部内部类(有类名)
- 匿名内部类(没有类名)
定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
局部内部类的使用
定义在外部类的局部位置,比如方法中,并且有类名
可以直接访问内外部类的所有成员,包含私有的
class Outer02{ private int n1=100; private void m2(){} public void m1(){ class Inner02{ //局部内部类 public void f1(){ System.out.println("n1="+n1); //访问了外部类的n1(私有属性) m2(); //访问了外部类的m2(私有方法) } } } }
不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的。但是可以使用final修,因为局部变量也可以使用final
class Outer02{ public void m1(){ public class Inner02{ //报错,局部内部类不能添加访问修饰符 } } }
class Outer02{ public void m1(){ final class Inner02{ //此处使用final修饰 } class Inner03 extends Inner02{} //报错,final修饰后无法继承 } }
作用域:仅仅在定义它的方法或代码块中
局部内部类—访问—>外部类的成员[访问方式:直接访问]
class Outer02{ private int n1=100; private void m2(){} public void m1(){ class Inner02{ //局部内部类 public void f1(){ System.out.println("n1="+n1); //访问了外部类的n1(私有属性) m2(); //访问了外部类的m2(私有方法) } } } }
外部类—方法—>局部内部类的成员[访问方式:创建对象,再访问(注意:必须在作用域内)]
class Outer02{ private int n1=100; private void m2(){} public void m1(){ final class Inner02{ //局部内部类 public void f1(){ System.out.println("n1="+n1); m2(); } } Inner02 inner02 = new Inner02(); //先创建对象 inner02.f1(); //再访问内部类的成员 } }
外部其他类—不能访问—>局部内部类(因为局部内部类地位是一个局部变量)
如果外部类和局部内部类的成员重名时,默认遵守就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
class Outer02{ private int n1=100; public void m1(){ final class Inner02{ //局部内部类 private int n1=800; public void f1(){ System.out.println("n1="+n1); //输出n1=800(内部类n1) System.out.println("n1="+Outer02.this.n1); //输出n1=100(外部类n1) } } } }
注意:
- 局部内部类定义在方法中/代码块
- 作用域在方法体或者代码块中
- 本质仍然是一个类
匿名内部类
匿名内部类是定义在外部类的局部位置,比如在方法中,并且没有类名
- 本质是类
- 内部类
- 该类没有名字
- 同时还是一个对象
基本语法:
new 类或接口(参数列表){
类体
};
匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征。也有创建对象的特征。对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法
可以直接访问外部类的所有成员,包含私有的
不能添加访问修饰符,因为它的地位就是一个局部变量
作用域仅仅在定义它的方法或代码块中
匿名内部类—访问—>外部类成员[访问方式:直接访问]
外部其他类—不能访问—>匿名内部类(因为匿名内部类是一个局部变量)
如果外部类和匿名内部类的成员重名时,匿名内部类去访问的话,默认遵循就近原则,如果想访问外部类成员,则可以使用(外部类名.this)去访问
成员内部类
可以直接访问外部类所有成员,包括私有的
可以添加任意访问修饰符,因为它的地位就是一个成员
作用域和其它外部类的其它成员一样,为整个类体
外部成员访问成员内部类
// 第一种方式 outer08.new Inner08(),相当于把new Inner08()当做是outer08成员 Outer08.Inner08 inner08 = outer08.new Inner08(); inner08.say(); // 第二种方式 在外部类中,编写一个方法,可以返回Inner08对象 Outer08.Inner08 inner08Instance = outer08.getInner08Instance(); inner08Instance.say();
静态内部类
- 与成员内部类基本相同,只不过增添一个static的前提
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
枚举和注解
枚举介绍
枚举是一组常量的集合
属于一种特殊的类,里面只包含一组有限的特定的对象
自定义类实现枚举
- 构造器私有化,防止直接
new
- 不需要提供
setxxx
方法,因为枚举对象值通常为只读 - 对枚举对象/属性使用
final+static
共同修饰,实现底层优化 - 枚举对象名通常使用全部大写,常量的命名规范
- 枚举对象根据需要,也可以有多个属性
enum关键字实现枚举
- 使用关键字
enum
时,会隐式的继承Enum
类、不能再extends
其他类,但是可以实现接口 - 多个常量用逗号
,
隔开 - 要求将定义常量对象写在前面
- 如果使用的是无参构造器,创建常量对象,可以省略
()
enum常用方法
toString()
:Enum 类已经重写过了Object类的toString 方法,返回的是当前对象名name()
:返回当前对象名(常量名),子类中不能重写ordinal()
:返回当前对象的位置号,默认从 0 开始values()
:返回当前枚举类中所有的对象数组valueOf()
:将字符串转换成枚举对象,要求传入的字符串必须 为已有的对象名,否则报异常compareTo(Enum e)
:比较两个枚举常量,比较是位置号
JDK内置的基本注解类型
@Override
表示指定重写父类的方法(在编译层面),如果不是则会报错
不写
@Override
,但构成方法的重写也不影响如果写了
@Override
又没重写就会报错
@Deprecated
@Deprecated
修饰某个元素,表示该元素已经过时不推荐使用,但仍然可以使用
从
@Deprecated
源码可以看出,可以用来修饰方法,类,包,字段,参数,等等@Deprecated
可以做版本升级过渡使用
@SuppressWarnings
当我们不希望看到警告的时候,可以用
@SuppressWarnings
来抑制警告信息在{“ “}中,可以写入你希望抑制的警告信息
作用范围与
@SuppressWarnings
的位置有关
异常
选中代码后ctrl + alt + t
语法错误和逻辑错误不是异常
异常体系图
常见运行时异常
NullPointerException
空指针异常当应用程序试图在需要对象的地方使用null时,抛出该异常
ArithmeticException
数学运算异常当出现异常的运算条件时,抛出此异常(比如除数为0的除法)
ArrayIndexOutOfBoundsException
数组下标越界异常用非法索引访问数组时,抛出的异常
ClassCastException
类型转换异常当试图将对象强制转换为不是实例的子类时,抛出此异常
NumberFormatException
数字格式不正确异常当试图将字符串转换成一种数值类型,但该字符不能转为适当格式时,抛出异常
常见编译时异常
SQLException
操作数据库时,查询表可能发生异常
IOException
操作文件时,发生的异常
FileNotFoundException
当操作一个不存在的文件时,发生异常
ClassNotFoundException
加载类,而该类不存在时,异常
EOFException
操作文件,到文件未尾,发生异常
ILLegalArgumentException
参数异常
try-catch-finally
try {
//可疑代码
//将异常生成对应的异常对象,传递给catch块
} catch (Exception e) {
//异常发生时
//系统将异常封装成Exception 对象e,传递给catch
//得到异常对象后,程序员自行处理
} finally{
//不管try代码块是否有异常,始终执行finally
//通常在这里释放资源
}
- 可以有多个
catch
语句,捕获不同的异常,(进行不同的业务处理),但是要求父类异常在后,子类异常在前 - 可以进行
try-finally
配合使用,这种相当于没有捕获异常,因此程序会退出
throws
try-catch-finally
和throws
二选一
默认是throws
编译异常必须处理
运行时的异常,程序中没有处理,默认就是
throws
子类重写的方法,抛出的异常类型要么和父类一致,要么是父类抛出的异常类型的子类型
在
throws
过程中,如果由try-catch
,就相当于有异常处理,不必throws
自定义异常
- 如果继承
Exception
,属于编译异常; - 如果继承
RuntimeException
,属于运行异常(一般来说,继承RuntimeException
),即把自定义异常做成运行时异常,好处是,我们可以显式的使用默认的处理机制;
public class CustomException {
public static void main(String[] args) {
int age=1;
if (!(age>=18&&age<=120)){
throw new AgeException("年龄需要在18~120之间");
}
System.out.println("你的年龄范围正确");
}
}
//自定义一个异常
class AgeException extends RuntimeException{
public AgeException(String message) {
super(message);
}
}
throw、throws区别
常用类
包装类
装箱和拆箱
JDK5
以前手动装箱和拆箱,JDK5
以后自动装箱和拆箱- 自动装箱底层调用的是
valueOf
方法,比如Integer.valueOf()
int n1=100;
//手动装箱
Integer integer=new Integer(n1);
Integer integer1=Integer.valueOf(n1);
//手动拆箱
int i=integer.intValue();
int n2=200;
//自动装箱
Integer integer2=n2;
//自动拆箱
int n3=integer2;
三元运算符是一个整体,类型转为高一级别的
Object obj1=true? new Integer(1):new Double(2.0); //类型转为高一级别的Double
System.out.println(obj1); //输出的结果是1.0
Object obj2;
if (true){
obj2=new Integer(1);
}
else {
obj2=new Double(2.0);
}
System.out.println(obj2); ////输出的结果是1
Integer类
底层缓存了-128~127
的值
如果有基本数据类型,判断的则是值是否相同
Integer a=127;
int b=127;
System.out.println(a==b); //比值,True
String类
使用
Unicode
字符编码,一个字符(不区分字母还是汉字)占两个字符可以串行化(可以在网络传输),可以比较大小
String
是final
类,不能被其他的类继承String
有属性private final char value[];
用于存放字符串内容value
是一个final
类型,(地址)不可以修改:不能指向新的地址,但单个字符内容可以变化
创建String对象的两种方式
直接赋值
String s = "dzr";
先从常量池查看是否有
"dzr"
的数据空间,如果有,直接指向;如果没有,则重新创建,然后指向s
最终指向的是常量池的空间地址
调用构造器
String s2 = new String("dzr");
先在堆中创建空间,里面维护了
value
属性,指向常量池的dzr
空间如果常量池没有
"dzr"
,重新创建,如果有,直接通过value指向最终指向的是堆中的空间地址
String a = "dzr";
String b =new String("dzr");
System.out.println(a.equals(b)); //T
System.out.println(a==b); //F
System.out.println(a==b.intern()); //T
System.out.println(b==b.intern()); //F
public String intern()
:最终返回的是常量池的地址(对象)
String a = "hello";
String b = "abc";
String c = a + b;
String d = "helloabc";
System.out.println(c == d);//F
String e = "hello" + "abc";//e指向常量池
System.out.println(d == e);//T
String c = a + b
的底层:
先创建一个 StringBuilder sb = StringBuilder()
,执行sb.append("hello");
然后执行sb.append("abc");
再执行 String c= sb.toString()
,最后c
指向堆中的对象(String) value[]
指向池中 "helloabc"
总结:
常量相加,看的是池;变量相加,看的是堆
class Test1 {
String str = new String("dzr");
final char[] ch = {'j', 'a', 'v', 'a'};
public void change(String str, char ch[]) {
str = "java";
ch[0] = 'h';
}
public static void main(String[] args) {
Test1 ex = new Test1();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.println(ex.ch);
}
}
//最终输出dzr and hava
常用方法:
equals
前面已经讲过了. 比较内容是否相同,区分大小写
equalsIgnoreCase
忽略大小写的判断内容是否相等
indexOf
获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
lastIndexOf
获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
substring
截取指定范围的子串
trim
去前后空格
charAt
获取某索引处的字符
toUpperCase
转换成大写
toLowerCase
转换成小写
concat
拼接字符串
replace
替换字符串中的字符
split
分割字符串
compareTo
比较两个字符串的大小,如果前者大,则返回正数,如果相等,返回0
以下内容于2023.3.1开始继续更新
StringBuffer和StringBuilder类
StringBuffer类
基本介绍
可变的字符序列,可以对字符串内容进行增删
很多方法与String相同,但是StringBuffer是可变长度的
String和StringBuffer区别
String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址(传创建新对象),效率较低;//private final char value[];其中字符串常量在常量池中
StringBuffer保存的是字符串变量,里面的值可以更改,每次StringBuffer的更新实际上可以更新内容,不用每次更新地址(除非分配的内存不够了,需要重新分配),效率较高;//char[] value;其中字符串变量在堆中
String 和 StringBuffer 相互转换
public class StringAndStringBuffer {
public static void main(String[] args) {
//String——>StringBuffer
String str = "字符串";
//方式1 使用构造器
//注意: 返回的才是StringBuffer对象,对str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式2 使用的是append方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("字符串");
//方式1 使用StringBuffer提供的 toString方法
String s = stringBuffer3.toString();
//方式2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}
StringBuffer 类常见方法
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(",张三丰");//"hello,张三丰"
//删
s.delete(11, 14);//删除 11~14的字符 [11, 14)
//改
s.replace(9, 11, "周芷若");//替换索引9-11的字符 [9,11)
//查
int indexOf = s.indexOf("张三丰");//查找第一次出现的索引,如果找不到返回-1
//插
s.insert(9, "赵敏");//在索引为9的位置插入"赵敏"
//长度
System.out.println(s.length());
}
}
StringBuilder类
基本介绍
此类被设计用作StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议优先采取该类,因为在大多数实现总,它比StringBuffer要快
在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接受任意类型的数据
String、StringBuffer和StringBuilder的比较
StringBuilder 和StringBuffer 非常相似,均代表可变的字符序列,而且方法也一样;
String:不可变字符序列,效率低,但是复用率高;
StringBuffer:可变字符序列、效率较高(增删)、线程安全(适合多线程);
StringBuilder:可变字符序列、效率最高、线程不安全(适合单线程);
效率:StringBuilder > StringBuffer > String
String、StringBuffer和StringBuilder的选择
如果字符串存在大量的修改操作,一般男使用StringBuffer和StringBuilder
如果字符串存在大量的修改操作,并在单线程的情况,使用StringBuilder
如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer
如果字符串存在很少量的修改操作,被多个对象引用,使用String,比如配置信息等
Math类
常见方法
public class MathMethod {
public static void main(String[] args) {
//1.abs 绝对值
int abs = Math.abs(-9);
//2.pow 求幂
double pow = Math.pow(2, 4);//2的4次方
System.out.println(pow);//16
//3.ceil 向上取整,返回>=该参数的最小整数(转成double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0
//7.random 求随机数
//random 返回的[0,1)之间的一个随机小数
//8.max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
}
}
Date日期类、Calendar日历类以及新的日期
第一代日期类
public class Date01 {
public static void main(String[] args) throws ParseException {
//1. 获取当前系统时间
//2. 这里的Date 类是在java.util包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
Date d2 = new Date(9234567); //通过指定毫秒数得到时间1970-1-1
//1. 创建 SimpleDateFormat对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);//2023年03日01月11:14:41 周三
//1. 可以把一个格式化的String 转成对应的 Date
//2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));
}
}
第二代日期类
Calendar类
public class Calendar_ {
public static void main(String[] args) {
//Calendar是一个抽象类, 并且构造器是private
//可以通过 getInstance() 来获取实例
//如果我们需要按照 24小时进制来获取时间,
//Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //获取日历类对象
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );
}
}
第三代日期类
LocalDate(年月日)、LocalTime(时分秒)、LocalDateTime(年月日时分秒)
JDK8
加入的
LocalDate
只包含日期,可以获取日期字段;
LocalTime
只包含时间,可以获取时间字段;
LocalDateTime
包含日期+时间,可以获取日期和时间字段
DateTimeFormatter 格式日期类
DateTimeFormat dtf = DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);
更多的方法
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期
//1. 使用now() 返回表示当前日期时间的对象
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
//2. 使用DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//plus和minus方法可以对当前时间进行加或者减
//890天后
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));
//3456分钟前
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
}
Instant时间戳
public class Instant_ {
public static void main(String[] args) {
//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();
System.out.println(now);
//2. 通过 from 可以把 Instant转成 Date
Date date = Date.from(now);
//3. 通过 date的toInstant() 可以把 date 转成Instant对象
Instant instant = date.toInstant();
}
}
System类
常用方法
exit:退出当前程序
arraycopy:赋值数组元素,比较适合底层调用,一般使用Array.copyOf完成复制数组
int[] f1 = {1,2,3}; int[] f2 = new int[3]; System.arraycopy(f1,0,f2,0,3);
currentTimeMillens:返回当前时间距离1970-1-1的毫秒数;
gc:运行垃圾回收机制
public class System_ {
public static void main(String[] args) {
//exit 退出当前程序
System.exit(0);
//源数组
//从源数组的哪个索引位置开始拷贝
//目标数组,即把源数组的数据拷贝到哪个数组
//把源数组的数据拷贝到目标数组的哪个索引
//从源数组拷贝多少个数据到目标数组
System.arraycopy(src, 0, dest, 0, src.length);
//currentTimeMillens:返回当前时间距离1970-1-1 的毫秒数
System.out.println(System.currentTimeMillis());
//运行垃圾回收机制
System.gc();
}
}
Arrays类
- toString 返回数组的字符串形式
- sort 排序(自然排序和定制排序)
- binarySearch 通过二分搜索法进行查找,要求必须排好序
- copyOf 数组元素的赋值
- fill 数组元素的填充
- equals 比较连个数组元素内容是否完全一致
- asList 将一组值,转换为list
BigInteger类和BigDecimal类
BigInteger类
适合保存比较大的整型
public class BigInteger_ {
public static void main(String[] args) {
BigInteger bigInteger = new BigInteger("237888888999999");
BigInteger bigInteger2 = new BigInteger("10099999999999");
System.out.println(bigInteger);
//对BigInteger 进行加减乘除需要使用对应的方法
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);//加
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(subtract);//减
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);//乘
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);//除
}
}
BigDecimal类
适合保存精度更高的浮点型(小数)
public class BigDecimal_ {
public static void main(String[] args) {
BigDecimal bigDecimal = new BigDecimal("1999.11");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal);
//对BigDecimal进行加减乘除需要使用对应的方法
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常ArithmeticException
//在调用divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
//如果有无限循环小数,就会保留 分子 的精度
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
}
}
集合
集合的理解
- 可以动态保存任意多个对象,使用比较方便
- 提供了一系列方便的操作对象的方法:add,remove,set,get等
- 使用集合添加,删除新元素的代码简洁
public static void main(String[] args) {
//Collection
//Map
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add("tom");
HashMap hashMap = new HashMap();
hashMap.put("NO1", "北京");
hashMap.put("NO2", "上海");
}
}
Collection
特点
Collection实现子类可以存放多个元素,每个元素可以是Object
有些Collection的实现类,可以存放重复的元素,有些不可以
有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
Collection接口没有直接的实现子类,是通过它的子接口Set 和 List来实现的
常用方法
public static void main(String[] args) {
List list = new ArrayList();//接口可以指向实现该接口的类
//add:添加单个元素
list.add("lll");
list.add(1);//有自动装箱的过程 list.add(new Integer(1));
list.add(true);//有自动装箱的过程
System.out.println(list);//list里的元素都是对象
//remove:删除指定元素 【两种方式】
list.remove("111");//指定删除某个元素
list.remove(0);//按下标删除第一个元素
//contains:查找元素是否存在
System.out.println(list.contains("111"));
//有返回true,无返回false
//size:获取元素个数
System.out.println(list.size());
//isEmpty:判断是否为空
System.out.println(list.isEmpty());
//clear:清空
list.clear();
//addAll:添加多个元素
ArrayList list1 = new ArrayList();
list1.add("haha");
list1.add("3213");
list.addAll(list1);
System.out.println(list);
//containsAll:查找多个元素是否存在
System.out.println(list.containsAll(list1));//都存在返回true
//removeAll:删除多个元素
list.removeAll(list1);
}
}
Iterator迭代器遍历
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个实现了Iterator接口的对象,即可以返回一个迭代器
- Iterator 的结构
- Iterator仅用于遍历集合, Iterator本身不存放对象
public class CollectionIterator {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
//遍历col集合
Iterator iterator = col.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.pr intln("obj=" + obj);
}
}
}
class Book {
private String name;
private String author;
private double price;
...
}
for循环加强遍历元素
简化版的迭代器遍历
public class CollectionFor {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add(new Book("三国演义", "罗贯中", 10.1));
col.add(new Book("小李飞刀", "古龙", 5.1));
col.add(new Book("红楼梦", "曹雪芹", 34.6));
for (Object o : col) {
System.out.println("book=" + o);
}
}
}
List接口和常用方法
基本介绍
List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
List集合中的每个元素都有其对应的顺序索引(即一个整数型的序号记载其在容器中的文字hi,可以根据序号存取容器中的元素),即支持索引
public class List_ {
@SuppressWarnings({"all"})
public static void main(String[] args) {
//List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
list.add("dzr");
list.add("tom");
System.out.println("list=" + list);
//List集合中的每个元素都有其对应的顺序索引
System.out.println(list.get(3));//dzr
}
}
常用方法
void add(int index, Object ele)
:在index位置插入ele元素,没有index,默认在最后插入boolean addAll(int index, Collection eles)
:从index位置开始将eles中的所有元素添加进来Object get(int index)
:获取指定index位置的元素int indexOf(Object obj)
:返回obj在集合中首次出现的位置int lastIndexOf(Object obj)
:返回obj在当前集合中末次出现的位置Object remove(int index)
:移除指定index位置的元素,并返回此元素Object set(int index, Object ele)
:设置指定index位置的元素为ele , 相当于是替换.List subList(int fromIndex, int toIndex)
:返回从fromIndex到toIndex位置的子集合[fromIndex,toIndex)
ArrayList类
注意事项
- ArrayList可以加入null,并且可以有多个
- ArrayList 是由数组来实现数据存储的
- ArrayList 基本等同于Vector,除了ArrayList是线程不安全的(但执行效率高),在多线程情况下,不建议使用ArrayList
底层分析
- ArrayList中维护了一个object类型的数组elementData
- 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次田间,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
- 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
Vector
基本介绍
- Vector底层是一个对象数组,protected Object[] elementData
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 开发中,需要线程同步安全时,考虑使用Vector
public class Vector_ {
public static void main(String[] args) {
Vector vector = new Vector(8);
for (int i = 0; i < 10; i++) {
vector.add(i);
}
vector.add(100);
System.out.println("vector=" + vector);
}
}
Vector和ArrayList的比较
LinkedList底层结构
LinkedList的说明
- LinkedList底层实现了双向链表和双端队列的特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
LinkedList的底层操作机制
LinkedList底层维护了一个双向链表
LinkedList中维护了两个属性first 和 last 分别指向首节点和尾节点
每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率比较高
LinkedList 的增删改查和循环遍历案例
@SuppressWarnings({"all"})
public class LinkedListCRUD {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
System.out.println("linkedList=" + linkedList);
//演示一个删除结点的
linkedList.remove(); // 这里默认删除的是第一个结点
//linkedList.remove(2);
System.out.println("linkedList=" + linkedList);
//修改某个结点对象
linkedList.set(1, 999);
System.out.println("linkedList=" + linkedList);
//得到某个结点对象
//get(1) 是得到双向链表的第二个对象
Object o = linkedList.get(1);
System.out.println(o);//999
//因为LinkedList 是 实现了List接口, 遍历方式
System.out.println("===LinkeList遍历迭代器====");
Iterator iterator = linkedList.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println("next=" + next);
}
System.out.println("===LinkeList遍历增强for====");
for (Object o1 : linkedList) {
System.out.println("o1=" + o1);
}
System.out.println("===LinkeList遍历普通for====");
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i));
}
//源码阅读.
/* 1. LinkedList linkedList = new LinkedList();
public LinkedList() {}
2. 这时 linkeList 的属性 first = null last = null
3. 执行 添加
public boolean add(E e) {
linkLast(e);
return true;
}
4.将新的结点,加入到双向链表的最后
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
*/
/*
读源码 linkedList.remove(); // 这里默认删除的是第一个结点
1. 执行 removeFirst
public E remove() {
return removeFirst();
}
2. 执行
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
private E unlinkFirst(Node<E> f) {
// assert f == first && f != null;
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null; // help GC
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
*/
}
}
ArrayList 和 LinkedList 比较
ArrayList 和 LinkedList 比较
随机更新
因为看mybatis的时候用到了反射所以来补一补
反射
优点
可以动态的创建和适用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点
使用反射基本是解释执行,对执行速度有影响
反射调用优化
使用setAccessible
,禁用访问检查,true为禁用
Class类
常用方法
获取方式
一直一个类的全名,且该类在类路径下,可以通过
Class
类的静态方法forName()
获取,可能会抛出ClassNotFoundException
- Class cls1 = Class.forName(“java.lang.Cat”);
- 多用于配置文件,读取类全路径,加载类
若已知具体的类,通过类的
class
获取- Class cls2 = Cat.class;
- 多用于参数传递,比如通过反射得到对应构造器对象
已知某个类的实例,调用该实例的
getClass()
方法获取Class
对象- Class cls3 = 对象.getClass();//运行类型
- 通过创建好的对象,获取Class对象
其他方式:
ClassLoader cl = 对象.getClass().getClassLoader();
- class cls4 = cl.loadClass(“类的全类名”);
基本数据按如下方式得到
Class
类对象- Class cls5 = 基本数据类型.class;
基本数据类型可以通过
.TYPE
得到Class
类对象- Class cls6 = 包装类.TYPE
哪些类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface
- 数组
- enum
- annotation
- 基本数据类型
- void
类加载
基本说明
- 静态加载
- 编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载
- 运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性
类加载时机
- 当创建对象时(new)
- 静态加载
- 当子类被加载时,父类也加载
- 静态加载
- 调用类中的静态成员时
- 静态加载
- 通过反射
- 动态加载
反射获取类的结构信息
Class类
Field类
Method类
Constructor类
通过反射创建对象
调用类中的public修饰的无参构造器
调用类中的指定构造器
Class类相关方法
newInstance:调用类中的无参构造器,获取对应类的对象
getConstructor(Class…class):根据参数列表,获取对应的构造器对象
getDecalaredConstructor(Class…class):根据参数列表,获取对应的构造器对象
Constructor类相关方法
- setAccessible:暴破
- newInstance(Object…obj):调用构造器
通过反射访问类中的成员
访问属性
- 根据属性名获取Field对象
- Field f = class对象.getDeclaredField(属性名);
- 暴力破解
- f.setAccessible(true);
- f 是Field
- 访问
- f.set(o,值)
- 如果是静态属性,则set和get中的参数o,可以写成null
访问方法
- 获取对象
- Object o = class.newInstance();
- 暴力破解
- m.setAccessible(true);
- 访问
- Object returnValue = m.invoke(o,实参列表);
- 注意
- 如果是静态方法, 则invoke的参数o,可以写成null
Lambda表达式
格式
(匿名内部类被重写方法的形参列表)->{
被重写方法的方法体代码
}
注:->是语法形式,无实际意义
实例
@FunctionalInterface//加上这个注解必须是函数式接口,且只有一个抽象方法
interface People{
void run();
}
//简化之前的匿名内部类
People p = new People(){
@Override
public void run(){
System.out.println("小学生跑的很慢!");
}
};
//简化之后
People p = () -> {
System.out.println("小学生跑的很慢!");
};
也可以将其作为参数代入方法中去使用
//在原来的基础上定义一个pk方法
public static void pk(People p){
System.out.println("跑步情况如何:")
p.run();
}
//不使用Lambda的情况下:
pk(p);//需要在定义匿名内部类之后,将p代入;
//使用Lambda的情况:
People p = () -> {
System.out.println("小学生跑的很慢!");
};
pk(() -> {
System.out.println("小学生跑的很慢!");
});
//由于等号右边即是People创建的对象p,因此可以可以直接将其代入
省略规则
- 参数类型可以省略不写
//精简之前:
Arrays.sort(grade,new Comparator<Integer>(){
@Override
public int compare(Integer o1,Integer o2){
return o2 -o1;
}
});
//精简之后:
Arrays.sort(grade,(Integer o1,Integer o2) -> {
return o2 -o1;
});
- 如果只有一个参数,参数类型可以省略,同时()也可以省略
//简单引用一个单个参数的例子,不需要了解其具体含义
btn.addActionListener((ActionEvent e) -> {
System.out.println("我是简单的例子。");
});
//简化之后:
btn.addActionListener( e -> {
System.out.println("我是简单的例子。");
});
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写,同时要省略分号
//参照上一条规则的例子
btn.addActionListener( e -> {
System.out.println("我是简单的例子。");
});
//简化之后:
btn.addActionListener( e -> System.out.println("我是简单的例子。") );
- 如果Lambda表达式的方法体代码只有一行代码。可以省略大括号不写。此时,如果这行代码是return语句,必须省略return不写,同时也必须省略”;”不写
//参照第一条规则的代码
Arrays.sort(grade,(Integer o1,Integer o2) -> {
return o2 -o1;
});
//简化之后:
Arrays.sort(grade,( o1, o2)-> o2 -o1);