内部类
概念
- 在类的内部声明的类称之为内部类
在类的三个地方可以声明内部类
在方法里面可以定义内部类,不可以使用public修饰(局部内部类)
public static void main(String[] args) {
//在方法里面定义内部类,不可以使用public修饰
class MyInner1{
public void t1(){
System.out.println("我是方法中的内部类");
}
}
}
在方法外部,类的内部定义内部类,可以用public修饰,但是如果要在外部使用是使用static进行修饰(成员内部类)
public class MyOut {
public static void main(String[] args) {
//main方法
}
//在类中,方法之外定义内部类,可以用public修饰
public static class MyInner2{
public void t1(){
System.out.println("我是类中的内部类");
}
}
}
在类的外部声明,不可以用public修饰(外部内部类) ```java public class MyOut {
} //在类的外面,也可以定义内部类 class MyInner3{ public void t1(){ System.out.println(“我是类外的内部类”); } }
总结:在方法的里面和在类的外部声明内部类,不可以使用public。在方法的外面,类的里面声明内部类可以使用public,但是如果外面使用此内部类,则需要给内部类加上static。
<a name="UtJoU"></a>
### 内部类的创建和调用
1. 所有的内部类都可以在声明的类中创建
```java
public class MyOut {
public void main() {
//在方法里面定义内部类,不可以使用public修饰
class MyInner1{
public void t1(){
System.out.println("我是方法中的内部类");
}
}
MyInner1 myInner1 = new MyInner1();
MyInner2 myInner2 = new MyInner2();
MyInner3 myInner3 = new MyInner3();
}
//在类中,方法之外定义内部类,可以用public修饰
public class MyInner2{
public void t1(){
System.out.println("我是类中的内部类");
}
}
}
//在类的外面,也可以定义内部类
class MyInner3{
public void t1(){
System.out.println("我是类外的内部类");
}
}
- 在类中和方法外声明的内部类可以在其他类中创建(创建的时候必须带上外部类的名称)
MyOut.MyInner2 inner=new MyOut.MyInner2();
关于static对内部类的修饰
- static只能修饰成员内部类
- 主要作用就是防止成员内部类包含外部类的默认this指针引用
- 还可以使得成员内部类被其他的外部类创建(new)
如果成员内部类的外部类的方法被static进行修饰了,成员内部类也必须使用static修饰之后才使用使用
局部内部类的使用
局部内部类要使用所在方法的局部变量的时候,具备变量需要使用final进行修饰
匿名内部类
主要针对的是成员内部类的定义,可以直接使用new对象进行创建,而不需要专门声明成员内部类
必须要对类继承,或者是接口才可以创建对象
public class Test002 {
public static void main(String[] args) {
class MyInner{
public void run(){
System.out.println("我是局部内部类");
}
};
MyInner inner = new MyInner();
inner.run();
//简化局部内部的定义,就是匿名内部类,
//必须对类的继承,或者是接口的实现才可以使用
class MySon extends MyFather{
public int getMoney(){
return this.money;
}
}
new MySon().getMoney();
new Object(){
public int getMoney(){
return 1;
}
}.getMoney();
//继承的内部类的写法
new MyFather(){
public int getMoney(){
return this.money;
}
}.getMoney();
/////对接口局部类内部的声明
class MyClass implements MyInterface{
@Override
public void run() {
System.out.println("局部内部类实现的接口的调用");
}
};
//创建成员内部类
new MyClass().run();
//可以实现多个方法
new MyInterface(){
@Override
public void run() {
System.out.println("匿名内部类的写法");
}
}.run();
//lambdb表单的写法,必须要有引用类型,只能用于接口
//接口只可以一个方法
MyInterface myInterface = ()->{
System.out.println("lambda表达式的写法");
};
myInterface.run();;
}
}
面向对象语法梳理
所有的java代码都写在src中
- 在创建java代码之前,必须要创建包,包的规则是公司域名倒置
- 创建.java文件,首字母必须大写,只能是字母
- class:类
- interface:接口
- enum:枚举
- annotation:注解
- java文件组成部分
- 指定所在的包名
package com.java2022.entity;
- 如果要使用其他包下类或者接口,需要使用import将类或者接口进行导入
import com.java2022.entity.MyFather;
启示:类名的全称应该= 包名 + 类名java.lang这个包下面的类默认被导入了,所以String不导包也可以
- 每个java文件可以声明多个类、接口、枚举和注解,但是有且仅有一个public 接口、类、枚举、注解和文件名称一致
- 类文件:使用class定义的java文件
- 类的名称必须和文件名一致,而是public修饰的
- 不可以使用static,可以使用final(最后的)(不可以被继承)和abstract(抽象)(有abstract修饰的方法)
- 可以在类名的后面使用extends继承一个类
- 在类名后面使用implements(在extends后面)实现多个接口(逗号分隔)
- 类的最后必须要有一个括号{}
- 定义成员变量(属性)
- 可以不用初始化,具备默认值
- 基本类型的默认值
- char,int,short,byte,long都是0
- double,float都是0.0
- boolean是false
- 其他所有应用类型默认值都是null
- 基本类型的默认值
- 引用类,可以使用new 初始化创建对象
- 变量在赋值的时候,可以使用类的静态方法,和对象的方法,可以支持三元表达的
- 所有的成员变量都需要进行封装
- 如果用static修饰的变量是放在静态区(也叫方法区),可以被当前所有new出的对象共享
- 可以不用初始化,具备默认值
构造方法:和类的名字一致,没有返回值,可以重载
- 默认自带一个无参的构造方法,在重载之后,默认的构造方法会失效,建议一般都将默认的构造方法补上
一个对象在被创建的时候必须要调用自己构造方法 和 父类的构造方法
- 如果父类构造方法被重载了,没有默认无参构造方法,那么子类在创建是不符合规范的
- 必须要在子类的构造方法中的第一行通过super调用父类构造方法 ```java public class MyClass3 extends MyFather{
public MyClass3(){
super(10);
} public MyClass3(int a){
this();
// super(10);
} } ```
this是指向当前类创建的对象的引用,this指向是自己,super指向是父类
- this/super.属性/方法
- this/super()调用构造方法
- 一般方法
- 基本语法
<修饰符> <返回类型> <方法名称>(<参数类型1> <形参名称1>,<参数类型2> <形参名称2>){
//具体的代码
//如果返回类型不是void,那么必须要写return <对应返回类型的具体值>
}
2. 方法的分类
1. 普通方法:必须要通过new出对象之后才可以被调用
1. 被static修饰的方法:可以通过类名称.方法名称直接调用
1. public static void main(String[] args):一个java程序的**入口方法**
3. 方法的参数可以是没有,也可以是多个,还可以是可变
3. 方法的返回只有一个类型,不可以有多个
- 方法中的代码
- 可以声明具备变量(基本和引用类型)
- 对于引用类型,主要是通过new 创建对象进行使用
- 可以不使用引用类型,直接通过new创建对象
- 基本类型不可以直接使用,必须要声明
- 在调用一个方法有返回值的时候,可以直接放入到另外一个方法的形参中,对象也是如此
setPerson(new Person(“x”,123));
System.out.println(t2());
4. 方法类内部也是可以声明类(局部内部类)
1. 局部的内部类可以直接使用外部的成员变量和方法,因为默认情况下局部内部类保存一个外部类的引用
1. 局部的内部类可以调用外部方法的局部变量,但是这个变量必须要使用final进行修饰
public void test1(){
final int bb =0;//方法的局部变量
class MyObject{
public void testA1(){
MyClass3.this.aa++;
int cc = bb;
cc++;
}
}
}
5. 匿名内部类本质上对局部内部类的简化写法
- 必须要是继承或者是实现
//匿名内部类
new MyInterface002(){
@Override
public void run() {
System.out.println("代码运行");
}
}.run();
- lambda表达式
//当匿名内部类实现的是接口并且,接口只有一个方法的时候可以使用lambda表达式
MyInterface002 t1= ()->{
System.out.println("代码lambda运行了");
};
t1.run();
- 可以编写成员内部类
- 基本要求和局部内部类似
- 区别
- 可以使用public修饰,可以使得成员内部类可以被其他类调用
- 可以使用static修饰,可以坚决默认保存外部类的this引用的问题,以防止内存泄露
- 类中可以直接使用static编写代码块
- Object是所有的类、接口的根类,任何类没有明确实现extends Object也默认继承了
- 那么所有的类有可以使用或者是重写Object的方法
- Object声明引用可以指向任意对象
- 使用object引用可以接受方法的任意返回结果类型
使用object类型的形参,可以接收任意传入类型实参
//使用object类型的形参,可以接收任意传入类型实参
public void setName(Object myTeacher) {
}
//作为返回结果类型,可以接收任意返回类型
public Object getStudent(){
return new MyStudent();
}
常用方法
getClass(),获取当前对象的类的类
- hashCode(),可以获取当前对象的hashcode,用于在一个hash集合中判断两个对象是否一致
- toString(),默认返回对象的字符串表述,可以通过重写toString方法返回对象中所有属性的值,便于测试
- equals(), 主要可以通过重写来判断两个对象的值是否一致,如果使用==判断是两个对象地址是否一致
```java
@Override
public boolean equals(Object obj) {
// if(this.getClass()==obj.getClass()) { ////1判断两个对象是否是同一个引用
if(this==obj) {
return true;
}
//2判断obj是否null
if(obj==null) {
return false;
}
//3判断是否是同一个类型
// }//intanceof 判断对象是否是某种类型
if(obj instanceof Student) {
//4强制类型转换
Student s=(Student)obj;
//5比较熟悉
if(this.name.equals(s.getName())&&this.age==s.getAge()) {
return true;
}
}
return false;
}
5. finalize()方法,在对象被垃圾回收器,回收的时候调用方法,一般用于判断当前对象是否被内存释放了
1. 重写finalize方法
```java
public class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("当前对象被内存释放");
}
}
在代码释放内存
public static void main(String[] args) {
MyObject obj = new MyObject();
//将obj设置null
obj = null;
System.gc();//呼叫垃圾回收期,释放内存
System.out.println("程序结束了");
}
封装类
概念
- 所有的基本类型,都不可以设置为null,都会有一个默认的值。
- 这样在某些情况下无法判断,那么为了可以将基本类型设置为null,则需要一个类将基本类型进行封装,才可以判断是否为空
- 在jdk中已经为所有的基本类型,设置封装类(首字母上大写,除了整数Integer和字符Character) | 基本类型 | 包装类 | | —- | —- | | byte | Byte | | short | Short | | int | Integer | | long | Long | | float | Float | | double | Double | | char | Character | | boolean | Boolean |
应用
解决无法使用null进行判断问题
int i=100;
/*
if(i==null){//不能使用
}*/
//将一个基本类型转换成了封装类型
Integer integer = new Integer(i);
//就可以直接使用封装的引用类型判断是否为null
if(integer ==null){
System.out.println("当前integer为"+null);
}else{
//从封装类型中获取具体的值
System.out.println("存储的内容:"+integer.intValue());
}
自动装拆箱
//直接将基本类型的值赋值给封装类型的引用
//jdk会自动执行装箱
Integer integer2 = i;
//可以直接从封装引用中,将存储的基本类型
//拆箱出来赋值给基本类型
int result = integer2;
可以实现类型之间转换
//从string类型转换成数值类型
String aa= "11";
//封装类中有自带的方法可以进行转换
//将string转换成int
Integer a = Integer.parseInt(aa);
//先转换,然后再自定拆箱
double d = Double.parseDouble("5.3");
缓冲区
Java预先创建了256个常用的整数包装类型对象(范围是-128-127)。
- 在实际应用当中,对已创建的对象进行复用。
我们来看下基础类型的包装类的缓存,Integer 默认缓存 -128 ~ 127 区间的值,Long 和 Short 也是缓存了这个区间的值,Byte 只能表示 -127 ~ 128 范围的值,全部缓存了,Character 缓存了 0 ~ 127 的值。Float 和 Double 没有缓存的意义。
Integer num1 = 10;
Integer num2 = 10;
System.out.println(num1==num2);//因为是缓冲区所以相同
int n1 = 10;
int n2 = 10;
Integer num3 = 1000;
Integer num4 = 1000;
System.out.println(num3==num4);//因为不是缓冲区,所以不相同
System.out.println(num3.intValue()==num4.intValue());//将封装类中具体存储的值获取进行比较
字符串类
String常用方法
String中字符的遍历
String str = "你好同学";
for (int i=0;i<str.length();i++){
char ch = str.charAt(i);
System.out.println(ch);
}
判断字符串是否存在
String str = "查找str首次出现的下标,存在,则返回该下标;不存在,则返回-1";
//判断字符串是否存在
boolean result = str.contains("返回1");
System.out.println(result);
去掉前后的空格
String username = " 张 三 "; //= scanner.next();
username = username.trim();
if(username.equals("张三")){
System.out.println("用户存在");
}else{
System.out.println("对不起,没有这个用户");
}
比较后缀名
String str ="天龙八部.mp4";
String str2 = "绿色.mp3";
if(str.endsWith(".mp4")){
System.out.println("这个是电影");
}
if(str2.endsWith(".mp3")){
System.out.println("这个是音乐");
}
替换
String str = "张三是一个好人";
str = str.replace("好人","坏人");
System.out.println(str);
根据某个字符将字符串进行切割
String tel = "010-444-8888";
String[] result = tel.split("-");
for (int i=0;i<result.length;i++){
System.out.println(result[i]);
}
字符串常量池
为了防止字符串在使用的使用重复创建内存,还使用的一个常量池
- 当使用=赋值的时候,会直接从常量池中获取字符串,如果不存在则在常量 池创建一个常量,并且将地址返回
- 当使用new创建对象的时候会分别在堆和常量中创建两个对象
intern是将当前对象中存储数据在常量池中的地址进行返回
String str1 ="小明";//存储在常量池中
String str2 = "小明";//存储在常量中
//如果new,会在堆里面存储一份,也会常量池里面存一份
String str3 = new String("小明");
System.out.println(str1==str2);
System.out.println(str2==str3);
//intern是从常量池冲获取存储数据的地址
System.out.println(str2==str3.intern());
StringBuffer和StringBuilder(重点)
String在使用字符串拼接的时候,每次拼接都会创建一个新的内存空间,这样性能低下又浪费内存
String sql = "select";
sql += " *"; //都需要创建的一个新的空间
sql += "from";//都需要创建的一个新的空间
sql += " tb_user";//都需要创建的一个新的空间
sql += " where ";//都需要创建的一个新的空间
所以jdk推出了两个类StringBuffer和StringBuilder,在字符串拼接的时候不会创建新的内存空间,在拼接完毕之后在一次性创建
StringBuffer sb = new StringBuffer();
//使用stringbuffer添加字符串
sb.append("select");
sb.append(" *");
sb.append("from");
//在拼接完毕之后,统一创建一个新的字符串空间
String str = sb.toString();
StringBuffer和StringBuilder
- StringBuffer使用为了保证线程的安全,使用了synchronized关键字会多线访问这个块内存保证线程的安全,但是会影响性能
- StringBuilder没有使用锁,虽然不安全,但是性能高
- 如果没有多线程,优先使用StringBuilder;如果使用了多线程的话那么要使用StringBuilder
速度测试
String str="";
StringBuilder sb = new StringBuilder();
long start = System.currentTimeMillis();
for(int i=0;i<100000;i++){
str+="小王";
}
long end = System.currentTimeMillis();
System.out.println("String时间:"+(end-start));
///////////////////////////////////////
start = System.currentTimeMillis();
for(int i=0;i<100000;i++){
sb.append("小王");
}
String result = sb.toString();
end = System.currentTimeMillis();
System.out.println("StringBuilder时间:"+(end-start));
BigDecimal
主要保证计算小数的精度
- 由于java基本类型都是使用二进制进行计算,所以会出现一些不可控的精度问题 ```java double d1 = 1.0; double d2 = 0.9;
System.out.println(d1-d2);//0.09999999999999998
3. 提供BigDecimal来解决这个问题(创建的BigDecimal要使用字符串)
```java
double d2 = 0.9;
System.out.println(d1-d2);
//要传入的是string类型
//BigDecimal b1 = new BigDecimal("1.0");
//BigDecimal b2 = new BigDecimal("0.9");
BigDecimal b1 = new BigDecimal(d1+"");
BigDecimal b2 = new BigDecimal(d2+"");
//b1+b2
//b1.add(b2); +
//b1.subtract; -
//b1.multiply(); *
// b1.divide(); /
BigDecimal b3 = b1.subtract(b2);
System.out.println(b3);
除不尽的解决
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3");
//除不尽会报错
//System.out.println(b1.divide(b2));
//第一个参数是要被除的对象
//第二个参数是保留的小数位
//第三个参数是多余的数字如何进行处理
//HALF_UP:四舍五入
BigDecimal b3 = b1.divide(b2,3, RoundingMode.HALF_UP);
System.out.println(b3);
日期类
日期:年月日,时间:小时,分钟,秒
Date类的基本使用
//年,从1900年开始
int year = date.getYear();
//月 从0开始
int month = date.getMonth();
//日
int d = date.getDate();
//周几
int day = date.getDay();
//小时
int hour = date.getHours();
//分钟
int minutes = date.getMinutes();
//秒
int second = date.getSeconds();
System.out.println("当前时间:"+year+"年"+month+"月"+d+"日");
System.out.println("当前时间:"+hour+":"+minutes+":"+d+"");
System.out.println("当前时间:星期"+day);
Calendar日历类
//不需要new,直接通过静态方法获取Calendar的实例
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int date = calendar.get(Calendar.DATE);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minutes=calendar.get(Calendar.MINUTE);
int second =calendar.get(Calendar.SECOND);
int day = calendar.get(Calendar.DAY_OF_WEEK);
System.out.println("当前时间:"+year+"年"+(month+1)+"月"+date+"日");
System.out.println("当前时间:"+hour+":"+minutes+":"+second+"");
System.out.println("当前时间:星期"+(day-1));
时间的计算
- 无论是Date类,还是Calendar都不能直接进行计算
- 提出一个时间戳的概念可以用于计算
时间戳是一个整数(long),它的最小单位是毫秒,记录了1970开始到现在的每一毫秒
long now = calendar.getTime().getTime();//获取当前的时间戳
long time = now+ 1000*60*60*20; //计算新的时间
calendar.setTimeInMillis(time);//设置新的时间
可以使用Calendar设置新的时间,并获取新的时间戳,用于和当前时间进行计算获取倒计时
Calendar calendar = Calendar.getInstance();
long now = calendar.getTime().getTime();
//设置当个属性
calendar.set(Calendar.HOUR_OF_DAY,17);
calendar.set(Calendar.MINUTE,50);
//获取当前5.50的时间戳
long time = calendar.getTime().getTime();
long left = time - now;
System.out.println(left/1000/60);
实际开发的时候,用户输入的数据都是字符串,那么字符串要转换成日期类型,就需要使用SimpleDateFormat类进行格式化
可以将字符串转换日期
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd");
Date birthdate = sdf.parse(birthday);
long now = new Date().getTime();
System.out.println((now-birthdate.getTime())/1000/60/60/24/365);
也可以将日期转成字符串
System.out.println(new Date());
//定义格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
//进行转换
String str = sdf.format(new Date());
System.out.println(str);
常用时间模式字母: | 字母 | 日期或时间 | 示例 | | —- | —- | —- | | y | 年 | 2019 | | M | 年中月份 | 08 | | d | 月中天数 | 10 | | H | 1天中小时数(0-23) | 22 | | m | 分钟 | 16 | | s | 秒 | 59 | | S | 毫秒 | 367 |
System系统类
- System.out.println输出
- System.arraycopy(…): 数组拷贝
- System.currentTimeMillis(): 获取当前的系统时间
- System.gc():呼叫垃圾回收期进行内存的释放
- System.exit(0);退出当前程序