内部类
-
内部类的概念
在一个类的内部再定义一个完整的类,一般内部类和外部类都会有依赖(层次)关系。
//外部类 身体 class Body{ //内部类 头 class Head{} }
-
内部类的特点
-
编译之后可生成独立的字节码文件。
上述代码在编译之后,除了生成一个
Body.class
文件,还会生成一个内部类文件Body$Head.class
。 -
内部类可以直接访问外部类的私有成员,而不破外封装性。
class Body{ private int headNum=1; class Head{ public void show() { //直接访问,没有问题 System.out.println(headNum); } } }
-
可为外部类提供必要的内部功能组件。
比如Head作为Body的“组件”。
-
-
内部类的分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
-
在类的内部定义的,与实例变量、实例方法同级别的类。
-
作为外部类的一个实例部分,创建内部类对象时,必须依赖外部类对象。
public class Outer { //实例变量 private String name="tang"; private int age=21; class Inner{ private String phone="110"; private String address="四川"; public void show() { //访问外部类 System.out.println(name); System.out.println(age); //访问内部类 System.out.println(phone); System.out.println(address); } } }
public class testOuter { public static void main(String[] args) { //1.创建外部类对象 Outer outer=new Outer(); //2.创建内部类对象 Inner inner=outer.new Inner(); //一步到位 Inner inner2=new Outer().new Inner(); inner.show(); } }
-
当外部类、内部类存在同名属性时,会优先访问内部类属性。
在Inner类中新增属性
private int age=18;
再次运行testOuter类,控制台打印的age属性为18而不是21。
那么如何在Inner内部类中访问外部类的同名属性呢?很简单:
//这表示访问Outer类的当前对象的age System.out.println(Outer.this.age);//21 //这表示访问当前类对象的age System.out.println(this.age);//18
第二句话中不加this也可以,但是加了会更清楚访问的是哪个类中的属性。
-
成员内部类不能定义静态成员。
例外就是可以包含final修饰的静态常量。
静态内部类
- 在成员内部类的基础之上添加一个static关键字,就变成了静态内部类。
- 不依赖外部对象,可直接创建或通过类名访问,可声明静态成员。
/**
*演示静态内部类
*/
public class Outer {
private String name="tang";
private int age=21;
//静态内部类,相当于一个外部类
static class Inner{
private String phone="110";
private String address="四川";
public void show() {
//访问外部类的属性,需要创建外部类对象
Outer outer=new Outer();
System.out.println(outer.name);
System.out.println(outer.age);
//调用静态内部类的属性和方法,直接使用
System.out.println(phone);
System.out.println(address);
}
}
}
public class testOuter {
public static void main(String[] args) {
//静态内部类直接创建
//外部类.内部类 仅表示一种包含关系
Outer.Inner inner=new Outer.Inner();
inner.show();
}
}
**注:**只有内部类才可以用static修饰,普通的类(外部类)不可以。
局部内部类
-
类似局部变量,定义在外部类方法中;作用范围和创建对象范围仅限于当前方法。
/** * 演示局部内部类 */ public class Outer { private String name="tang"; private int age=21; public void show() { //定义局部变量 String address="四川"; //局部内部类,不能加任何访问修饰符 class Inner{ private String phone="110"; private String email="99@qq.com"; public void innerShow() { //直接访问外部类的属性 System.out.println(name); //其实上一句省略了前缀,建议以后使用时加上。 System.out.println(Outer.this.age); //访问内部类属性 System.out.println(phone); //上一句省略了this System.out.println(this.email); } } } }
public class testOuter { public static void main(String[] args) { //创建外部类对象 Outer outer=new Outer(); //直接使用show方法,会有输出结果吗? outer.show(); } }
在testOuter中,创建了一个外部类对象调用其show方法,不会有任何结果,因为在show方法中,只是定义了一个局部变量和一个类,除此之外没有做任何事。如果要调用innerShow方法,想想该怎么修改代码?
public class Outer { public void show() { class Inner{ public void innerShow() { } } //要调用局部内部类的方法,就要创建局部内部类对象 Inner inner=new Inner(); inner.innerShow(); } } }
这时候重新运行代码,控制台就正常打印了。
-
局部内部类访问外部类当前方法中的局部变量时,因无法保障变量的生命周期与自身相同,变量必须修饰为final。
举个例子,看上文的Outer类代码段,在JDK1.7之前,在局部内部类Inner的innerShow方法中添加代码:
System.out.println(address);//访问局部变量
这是不行的,除非address变量被final修饰成为一个常量。因为方法中的局部变量在离开方法体之后就会被立即销毁,而在方法体中new的局部内部类对象却不会立即消失(当然inner这个变量是没有了),而是留在堆中等待回收。想一想,局部内部类对象还存在,而他所访问的局部变量被销毁了,这是不可行的。而局部变量被final修饰之后就变成了一个常量,上文所添加的代码就相当于:
``System.out.println(“四川”);`
在JDK1.8中,局部变量即使没有被final修饰,在局部内部类使用时IDE也不会报错,这是因为在JDK1.8中会自动添加final修饰词。
匿名内部类
-
没有类名的局部内部类(一切特征都与局部内部类相同)。
实际上只是我们定义的时候没有取名字罢了,编译器编译之后会自动取一个名字。
-
必须继承一个父类或者实现一个接口。
这不仅是使用的要求,这也是一般情况下使用匿名内部类的目的。
演示一下匿名内部类的用法:
/**
* 一个接口
*/
public interface USB {
void work();
}
COPY/**
* 一个实现接口的类
*/
public class Mouse implements USB{
@Override
public void work() {
System.out.println("鼠标正在工作。");
}
}
COPYpublic class testUSB {
public static void main(String[] args) {
USB usb=new Mouse();
usb.work();
}
}
这是正常情况下我们新建一个实现类,可以让我们在不同类中多次使用。但是如果某个实现类只需要用到一次,之后就不会再用了,如果还是单独建一个class来实现的话是否麻烦且多余了?这时候就可以简化成局部内部类:
public class testUSB {
public static void main(String[] args) {
class Phone implements USB{
@Override
public void work() {
System.out.println("手机已连接。");
}
}
Phone phone=new Phone();
phone.work();
}
}
因为实现类只使用一次,类名看起来也是多余的,那么再精简优化一下就变成了匿名内部类:
public class testUSB {
public static void main(String[] args) {
//在new的接口中实现方法,这里也可以是一个抽象方法或者一个父类
USB phone=new USB() {
@Override
public void work() {
System.out.println("手机已连接。");
}
};
phone.work();
}
}
-
它是定义类、实现类、创建对象的语法合并,只能创建一个该类的对象。
-
优点是减少代码量,缺点是可读性差。
我们可以查看一下本地生成的class文件,发现会一个
testUSB$1.class
文件,这个1就可以理解是匿名内部类的名字。
Object类
-
超类、基类,所有类的直接或间接父类,位于继承树的最顶层。
-
任何类,如果没写extends关键字继承某个类,那么默认的就是直接继承Object类。
-
Object类中所定义的方法,是所有对象都具备的方法。
-
Object类型可以存储任何对象
。
- 作为参数,可接受任何对象。
- 作为返回值,可返回任何对象。
getClass()方法
public final Class<?> getClass(){}
- 返回引用中存储的实际对象类型。
- 应用:通常用于判断两个引用中实际存储对象类型是否一致。
/**
*演示getClass方法的使用
*/
public class Demo1 {
public static void main(String[] args) {
String s1="aaa";
String s2="bbb";
Class class1=s1.getClass();
Class class2=s2.getClass();
if(class1==class2) {
System.out.println("属于同一类");
}else {
System.out.println("不属于同一类");
}
}
}
hashCode()方法
-
pubilc int hashCode(){}
-
返回该对象的哈希码值。
-
哈希值根据对象的地址或字符串或数字使用hash算法计算出来的int类型的数值。
-
一般情况下相同对象返回相同哈希码。
在我的另一篇有关集合的博客里就重写过这个方法使不同的对象返回相同的哈希码值。
public class Demo2 {
public static void main(String[] args) {
String s1="aaa";
String s2="bbb";
String s3="aaa";
System.out.println(s1.hashCode());//96321
System.out.println(s2.hashCode());//97314
//相同字符串生成的哈希值相同
System.out.println(s3.hashCode());//96321
}
}
toString()方法
-
public String toString(){}
-
返回该对象的字符串表示(表现形式)。
默认返回的是类名和一个十六进制表示的哈希值,也就是
getClass().getName() + '@' + Integer.toHexString(hashCode())
。 -
可以根据程序需求覆盖该方法,如展示对象的各个属性值。
/**
* 学生类
*/
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo3 {
public static void main(String[] args) {
Student s1=new Student("tang", 21);
//输出 包名.Student@15db9742
System.out.println(s1.toString());
}
}
一般在使用这个方式时不会直接调用Object的父类方法,而是重写成自己期望的输出,例如通过调用这个方法得知当前对象的属性值:
//Student类中重写方法
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
这时候再运行程序就是我们想看到的结果了。
equals()方法
public boolean equals(Object obj){}
- 默认实现为(this==obj),比较两个对象地址是否相同。
- 可进行覆盖,比较两个对象的内容是否相同。
/**
*演示equals的使用
*/
public class Demo3 {
public static void main(String[] args) {
Student s1=new Student("tang", 21);
Student s2=new Student("tang",21);
System.out.println(s1.equals(s2));//false
}
}
因为两个对象的地址不同所以两个对象不相等,如果两个对象属性相同便认为他们是同一个对象,可以重写equals代码,重写一般会有固定的步骤:
- 比较两个引用是否指向同一个对象。
- 判断obj是否为null。
- 判断两个引用指向的实际对象类型是否一致。
- 强制类型转换。
- 依次比较各个属性值是否相同。
也可以使用IDE的快捷功能自动重写,以下是eclipes的重写内容:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
然后再次运行代码,控制台就输出true了。
finalize()方法
-
当对象被判定为垃圾对象时,由JVM自动调用此方法,用以标记垃圾对象,进入回收队列。
-
垃圾对象
没有有效引用指向此对象时,为垃圾对象。
-
垃圾回收
由GC销毁垃圾对象,释放数据存储空间。
-
自动回收机制
JVM的内存耗尽,一次性回收所有垃圾对象。
-
手动回收机制
使用
System.gc();
通知JVM执行垃圾回收。
这个方法实际上是不包含任何代码,它是一个空的方法,你可以重写该方法来观察JVM是否回收了某些对象,此处不再演示。
-
包装类
-
什么是包装类?
基本数据类型所对应的引用数据类型。
-
Object可统一所有数据,包装的默认值为null。
基本数据类型 包装类型 byte Byte short Short int Integer long Long float Float double Double boolean Boolean char Character
装箱和拆箱
每个基本类型都对应着一个引用(包装)类型,基本类型存储在栈空间而引用类型存储在堆空间;把基础类型转换成引用类型的过程叫做装箱,每个引用类型都提供了一些方法和属性可供使用;把引用类型转换成基础类型的过程叫做拆箱。
/**
* 拆箱和装箱演示
*/
public class Demo1 {
public static void main(String[] args) {
//装箱(两种方法)
int num1=10;
Integer integer1=new Integer(num1);
Integer integer2=Integer.valueOf(num1);
//拆箱
int num2=integer1.intValue();
}
}
在JDK1.5之前,我们需要进行如上的操作来装箱和拆箱,但在JDK1.5之后,java就提供了自动装箱和拆箱的功能。
public static void main(String[] args) {
//装箱
int num1=10;
Integer integer1=num1;
//拆箱
int num2=integer1;
}
不必调用方法传值便可以实现装箱和拆箱。但实际上并不是不需要写,而是编译的时候java自动帮你做了这些工作。我们可以使用一个小工具Xjad来验证一下,这是一个反编译的工具,可以把class文件反编译成java代码,如果有需要可以自己百度下,这里只简单描述一下。运行上一段代码,将编译的class文件拖到这个小工具中,可以看到:
public static void main(String args[])
{
int num1 = 10;
Integer integer1 = Integer.valueOf(num1);
int num2 = integer1.intValue();
}
文件在编译后自动调用了Integer中的方法。
类型转换
-
8种包装类提供不同类型间的转换方式。
- Number父类中提供的6个共性方法。
- parseXXX()静态方法。
- valueOf()静态方法。
public class Demo2 { //基本类型和字符串之间的转换 public static void main(String[] args) { //1.基本类型转换成字符串 int n1=255; //1.1 使用+号 String s1=n1+""; //1.2 使用Integer中的tostring方法 String s3=Integer.toString(n1); String s2=Integer.toString(n1, 10);//第二个参数是基数,可以理解为x进制 //2.字符串转换成基本类型 String string="150"; int n2=Integer.parseInt(string); //字符串转换成boolean类型,"true"->"true" "非true"->"false" String string2="true"; String string3="123"; boolean b1=Boolean.parseBoolean(string2);//true boolean b2=Boolean.parseBoolean(string3);//false } }
整数缓冲区【重点】
-
Java预先创建了256个常用的整数包装类型对象。
什么意思呢?给大家三个问题,在继续往下看时,你先在心里给出一个答案:
public class Demo2 { public static void main(String[] args) { //1. 结果输出什么? Integer integer1=new Integer(100); Integer integer2=new Integer(100); System.out.println(integer1==integer2); //2.结果输出什么? Integer integer3=100; Integer integer4=100; System.out.println(integer3==integer4); //3.结果输出什么? Integer integer5=200; Integer integer6=200; System.out.println(integer5==integer6); } }
三个问题的结果分别是false,true,false。
先来回答第一个问题,两个存储在栈空间的变量分别指向的是两个堆空间中的对象,两个对象的地址不一样,所以在比较时返回了false。
第二个问题和第三个问题都是进行了自动装箱,为什么结果不一样?我们先来看看java是如何自动装箱的,同样借助Xjad小工具反编译Demo2.class文件,得到如下代码:
public static void main(String args[]) { Integer integer1 = new Integer(100); Integer integer2 = new Integer(100); System.out.println(integer1 == integer2); Integer integer3 = Integer.valueOf(100); Integer integer4 = Integer.valueOf(100); System.out.println(integer3 == integer4); Integer integer5 = Integer.valueOf(200); Integer integer6 = Integer.valueOf(200); System.out.println(integer5 == integer6); }
发现java自动装箱调用的是valueOf这个方法,重点来了,进入到这个方法的源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
可以看见该方法首先判断了传入值的范围,我们查看一下这个范围的大小,进入到IntegerCache类的源码(部分):
//以下代码只截取了部分,以便更好地分析与讲解 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { int h = 127; high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } }
IntegerCache字面意思就是整型缓冲区。该类定义了一个值为-128的变量low和一个值为127的变量high,还有一个数组大小为[(127+128)+1]=256的变量cache;并且,使用了一个for循环,用-128到127范围的值初始化了cache数组。
这时候再回到valueOf方法源码中,发现if判断的就是传入值是否在-128到127之间,是的话就直接返回cache数组中的对应值,也就是说,如果传入的数字在这个范围内,那么对应装箱的Integer对象其实已经是初始化过的,直接拿来用。如果valueOf方法传入的值不在这个范围,那么返回的就是用Integer构造方法new的一个对象。
所以第二个问题答案是true,它们所引用的就是cache数组中的同一个地址;而第三个问题中的变量不在cache范围内,所以所执行的代码同第一个问题。
可变字符串
- StringBuffer:可变长字符串,JDK1.0提供,运行效率慢、线程安全。
- StringBuilder:可变长字符串,JDK1.5提供,运行效率快、线程不安全。
这两个类相当于String的增强类,事先开辟了一块缓冲区;这两个类的用法是一样的,效率都比String高。
/**
* 演示StringBuilder常用方法的使用
* 效率比String高;比String节省内存
*/
public class Demo1 {
public static void main(String[] args) {
StringBuilder stringBuilder=new StringBuilder();
//1. append();追加
stringBuilder.append("我");//我
stringBuilder.append("菜");//我菜
//2. insert();插入
stringBuilder.insert(0, "前");//前我菜
//3. replace();替换
stringBuilder.replace(1, 2, "你");//前你菜
//4. delete();删除
stringBuilder.delete(0, 1);//你菜
System.out.println(stringBuilder.toString());
}
}
BigDecimal类
下面的输出结果是多少?
double b1=1.0;
double b2=0.9;
System.out.print(b1-b2);
正常的运算答案是0.1,而程序输出的结果是0.0999...98
。因为浮点类型存储的实际是一个近似值,经过计算之后肯定会有误差,只不过这种误差很小。
很多实际应用中需要精确计算,用double肯定不符合要求,这时候需要借助BigDecimal类来实现。
/**
* 演示BigDecimal类的使用
*/
public class Demo1 {
public static void main(String[] args) {
BigDecimal b1=new BigDecimal("1.0");
BigDecimal b2=new BigDecimal("0.9");
System.out.println(b1.subtract(b2));//减法 0.1
System.out.println(b1.add(b2));//加法 1.9
System.out.println(b1.multiply(b2));//乘法 0.90
System.out.println(b2.divide(b1));//除法 0.9
}
}
可以看到以上计算没有问题,但是要注意的是,如果除法的结果除不尽,那么就会报一个异常;所以在除法运算的时候需要使用divide的另外一个构造方法divide(divisor, scale, roundingMode)
。
- 参数divisor:除数
- 参数scale:指定精确到小数点后几位
- 参数roundingMode:
- 指定小数部分的取舍模式,通常采用四舍五入的模式。
- 取值为
BigDecimal.ROUND_HALF_UP
System.out.println(new BigDecimal("10").divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP));//3.33
System.out.println(new BigDecimal("20").divide(new BigDecimal("3"), 2, BigDecimal.ROUND_HALF_UP));//6.67
时间类
Date类
- Date表示特定的瞬间,精确到毫秒。Date类中的大部分方法都已经被Calendar类中的方法所取代(已过时)。
/**
* 演示Date类中尚未过时的方法
*/
public class Demo1 {
public static void main(String[] args) {
Date d1=new Date();
Date d2=new Date();
//当前时间 Wed Nov 04 12:01:52 CST 2020
System.out.println(d1.toString());
//已过时,打印当地时间
//2020-11-4 12:01:52
System.out.println(d1.toLocaleString());
//after before判断两个时间前后关系
d2=new Date(d1.getTime()-60*60*24*1000);//昨天此刻
System.out.println(d1.after(d2));//true 今天在昨天后面
System.out.println(d1.before(d2));//false
//compareTo比较,两者毫秒数相减,返回正负0
System.out.println(d1.compareTo(d2));//1
System.out.println(d2.compareTo(d1));//-1
System.out.println(d1.compareTo(d1));//0
//equals判断是否相等
System.out.println(d1.equals(d2));//false
}
}
Calendar类
-
Calendar提供了获取或设置各种日历字段的方法。
-
构造方法
protected Calendar()
由于修饰符是protected,所以无法直接创建该对象。
其他方法:
-
static Calendar getInstance()
使用默认时区和区域获取日历。
-
void set(int year,int month,int date,int hourOfDay,int minute,int second)
设置日历的年、月、日、时、分、秒。
-
int get(int field)
返回给定日历字段的值。字段比如年、月、日等。
-
void setTime(Date date)
用给定的Date设置次日历的时间。Date->Calendar
-
Date getTime()
返回一个Date表示此日历的时间。Calendar->Date
-
void add(int field,int amount)
按照日历的规则,给指定字段添加或减少时间量。
-
long getTimeMillies()
毫秒为单位返回该日历的时间值。
/**
* 演示Calendar类常用方法的使用
*/
public class Demo2 {
public static void main(String[] args) {
//创建Calendar对象
Calendar calendar=Calendar.getInstance();
//2020-11-4 13:50:56
System.out.println(calendar.getTime().toLocaleString());
//1604469056368
System.out.println(calendar.getTimeInMillis());
//获取时间信息
//年 2020
System.out.println(calendar.get(Calendar.YEAR));
//月 11 值为0-11
System.out.println(calendar.get(Calendar.MONTH)+1);
//日 4
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
//小时 1/13 12小时/24小时
System.out.println(calendar.get(Calendar.HOUR));
System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
//分钟 50
System.out.println(calendar.get(Calendar.MINUTE));
//秒 56
System.out.println(calendar.get(Calendar.SECOND));
//修改时间
calendar.set(Calendar.YEAR, 2019);
//2019-11-4 13:50:56
System.out.println(calendar.getTime().toLocaleString());
//添加或减少时间量
calendar.add(Calendar.MONTH, -1);
//2019-10-4 13:50:56
System.out.println(calendar.getTime().toLocaleString());
//获取时间字段的最大值、最小值
//31 / 1
System.out.println(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
System.out.println(calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
}
}
SimpleDateFormat类
-
SimpleDateFormat是一个以语言环境有关的方式来格式化和解析日期的具体类。
-
可以进行格式化(日期到文本)和解析(文本和日期)。
-
常用的时间模式字母:
字母 日期或时间 y 年 M 年中月份 d 月中天数 H 1天中小时数(0-23) m 分钟 s 秒 S 毫秒
public class Demo3 {
public static void main(String[] args) throws ParseException {
//创建对象
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
//创建Date对象
Date date=new Date();
//格式化
String string=simpleDateFormat.format(date);
//2020/11/04 14:10:21
System.out.println(string);
//解析(必须按照上面格式化的形式)
Date date2=simpleDateFormat.parse("1999/01/30 00:00:00");
//1999-1-30 0:00:00
System.out.println(date2.toLocaleString());
}
}
System类
-
系统类,主要用于获取系统的属性数据和其他操作,其构造方法是私有的。
-
常用方法
-
static void arraycopy(...)
复制数组
-
static long currentTimeMillis()
获取当前系统时间,返回的是毫秒值。通常用来计算某个操作的用时,操作前后各获取一个时间然后相减,所得的毫秒数就是用时。
-
static void gc()
建议JVM赶快启动垃圾回收器回收垃圾,具体是否调用是由系统决定的。
-
static void exit(int status)
退出JVM,如果参数是0表示正常退出JVM,非0表示异常退出JVM。
-
/**
* 演示arraycopy的使用
*/
public class Demo1 {
public static void main(String[] args) {
int[] src= {3,14,15,92,65,35,85};
int[] dest = new int[7];
int srcPos=0,destPos=0;
int length=src.length;
/**
* src:源数组
* scrPos:复制的源数组起始位置
* dest:目标数组
* destPos:复制的目标数组起始位置
* length:复制的数组长度
*/
System.arraycopy(src, srcPos, dest, destPos, length);
}
}
在Arrays类里也有一个copyOf复制数组的方法,这两个方法有什么区别呢?通过查看源码发现Arrays的这个方法内部调用的就是System.arraycopy()
方法,而arraycopy方法的源码是被native修饰的本地方法:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
可以看到方法体是空的,它是一个原生函数,并不是由java来实现的,而是由c/c++来实现的,java只是调用了它,由c++实现的这个方法效率会比java快很多。
转载地址:https://lazydog036.gitee.io/2020/11/04/JAVA常用类总结/