native关键字
1.native的意思是本地的,内置的,原生的。
在Java中表示本地的xx
2.适用范围
只能用于修饰某个方法。
3.用它修饰的方法有什么特点?
用它修饰的方法体,是用非Java语言实现的,主要是C语言。
在Java层面看不到它的方法体。
【其他修饰符】 native 返回值类型 方法名(【形参列表】); //没有方法体
用C实现的方法会编译为xx.dll以备调用。
//例如:
//System类中的部分代码:
public final class System {
//修饰符:private static native, private表示私有的,仅限于System类自己使用
// static表示静态的,只有静态的,它才可以在静态代码块中直接调用
// native表示该方法是本地方法,它的方法体不是Java语言实现的,是由C语言实现的。
private static native void registerNatives();
static {//静态代码块
registerNatives(); //调用了本类的一个静态方法
}
...
}
4.用Java语言实现的方法调用时有入栈和出栈的过程,在Java虚拟机栈分配内存,用C语言实现的方法调用时也有入栈和出栈。在本地方法栈分配内存。
5、在Java中如何使用native修饰的方法呢?
(1)和普通的Java方法一样调用。
(2)如果子类需要对native方法进行重写,也是可以的。
//例如:Object类:所有类的根父类
public class Object {
public native int hashCode(); //它也是一个native方法
...
}
*/
public class TestNative {
public static void main(String[] args) {
MyData my = new MyData();
System.out.println(my.hashCode());
}
}
//这个类没有显示声明它的父类,它的父类默认就是Object
class MyData{
//重写方法的快捷键Ctrl + O
@Override
public int hashCode() {
return 1234;//这里随便写的,符合语法的一个整数
}
}
final关键字
1、关键字的意思:最终的
2、可以用在哪里呢?
可以用它来修饰:类、方法、变量
3、用它修饰的类有什么不同?
用它修饰的类不能被继承,即不能有子类,我们称为“太监类”。例如:System类,String类,Math类....这些类都非常重要,它们是Java中最最核心的类型。
4、用它修饰的方法有什么不同?
用它修饰的方法可以被继承不能被子类重写。
例如:Object类不是final类,可以被子类继承。但是某个方法不希望被子类重写,那么就给这个方法单独加final。
5、用它修改的变量有什么不同?
用它修改的变量的值不能修改,称为最终变量,或者常量。这个变量可以是局部变量、静态变量、实例变量。
如果使用final修饰的静态变量或实例变量,必须有显式的初始化语句。
如果使用final修饰的静态变量或实例变量,没有set方法,只有get方法。
Object根父类
1、API:Application Program Interface,应用程序编程接口,可以被使用的一组类库。
Java SE API文档:核心类库的文档
Java SE API文档,每一个JDK版本都会在Oracle官网提供下载。都是一组网页文件(.html),并且都是英文的。 很多中国的程序员,就将它进行了翻译(汉化)(有的翻译的不是很好,需要用英文原文文档辅助理解)。 又因为一直网页文件不方便携带和分享,所以有人就把它们用工具合成了.chm的文件。
2、Object类是核心类库的其中一个类。
我们可以通过: (1)查看API文档简单快速的了解它 (2)查看src.zip的源码详细的了解它
3、API文档长什么样?
(1)包 (2)包下的类、接口等 (3)某个类、接口的详细解释。
4、Object类在java.lang包
Object类是类层次结构的根父类。
如何解读这句话?
(1)所有类都会直接或间接的继承Object类
(2)如果一个类没有明确它的父类是谁,那么默认它的直接父类是Object。
(3)Object中的成员方法、成员变量都会继承到子类中。所有对象(包括数组)都可以调用Object中的方法。
(4)所有对象的创建都会调用到Object的无参构造。
(5)Object类型的变量可以接收任意类型的对象,这个变量与其他类型的对象构成多态引用。
(6)Object类型的数组,可以存储任意类型的对象
注意:Object类型的变量可以接收一个基本数据类型的数组对象,
但是Object[]类型的变量,不能接收一个基本数据类型的数组对象。
==>很多API的重载方法,需要并存
public static void sort(int[] arr)
public static void sort(double[] arr)
public static void sort(Object[] arr) //Object[]数组的形参不能接收int[]类型的实参
5、Object类中有哪些方法?11个
Object之toString方法
1.public String toString():
修饰符:public,在任意位置可见
没有static,必须通过“对象."才能访问
没有final,说明子类可以重写
没有native,在源码中可以看到它的方法体实现
返回值类型:String
方法名:toString,表示用字符串表示xxx,这里xxx表示的是对象的信息。
2.默认toString方法的返回值
默认toString方法返回的是 getClass().getName() + "@" + Integer.toHexString(hashCode());
getClass().getName():当前对象的运行时类型的名称
Integer.toHexString(hashCode()):把对象的hashCode值,用十六进制形式表示
例如:com.atguigu.api.Student@4554617c
com.atguigu.api.Student:对象的运行时类型的名称
4554617c:对象的hashCode值,十六进制表示形式。
2.如果我们直接打印一个引用数据类型的变量(包括数组类型),默认就会自动调用它的toString方法。
因为引用数据类型的变量中存储的是对象的地址值,Java是不能对外暴露内存地址的, 它就用能代表对象信息的toString方法的返回值代替输出。
3.结果应是一个简明但易于读懂的信息表达式,建议所有子类都重写此方法。
4.如何重写?
idea有模板,Alt + Insert
Object之getClass()方法
1.public final native Class<?> getClass():功能,返回对象的运行时类型。
修饰符:public 公共的,任意位置可见 final 表示子类不能重写 没有static,必须通过“对象."才能访问 返回值类型:Class 其中暂时先不管,它是泛型
Class:是一种数据类型,首字母是大写的,区别于关键字class。这种数据类型的对象代表一种数据类型。Java会把所有数据类型加载到方法区的内存,并且每一种数据类型信息用一个Class对象存储起来。
2作用
与类的反射有关。
public class TestGetClass {
public static void main(String[] args) {
Object obj = "hello";
//obj的编译时类型是Object类型
//obj的运行时类型是String
System.out.println(obj.getClass());//class java.lang.String
Object arr = new int[5];
// arr[0] = 12;//错误
// arr的编译时类型是Object,相当于new int[5]被向上转型,所以只能调用父类Object有的成员方法,成员变量,
//即失去了数组的特点。
}
}
Object方法之 finalize()
1.protected void finalize() throws Throwable:
修饰符:protected 可见性范围是 本类、本包(包括子类和非子类)、其他包的子类
换句话说,除了其他包的非子类都可以访问。
抛出异常:throws Throwable 后面异常章节再说
2.功能
功能是用于垃圾回收器在回收垃圾对象之前做一些清理工作的。
比喻:给垃圾对象留临终遗言或交代后事的。
在实际开发中,用于资源对象做释放资源等清理工作。
比如:IO流的类型的对象,在IO流对象使用完成之后,需要释放资源。
IO流是和文件等系统资源传输数据用,读文件,写文件,都需要用到IO流。
这里释放资源,不仅仅是JVM中对象的内存回收问题,还有操作系统层面的资源释放问题。
3.是否可以重写
子类可以重写,通常是资源类需要重写,IO流,数据库连接,网络连接等等资源类需要重写,普通的业务类是不需要重写它的。
4.面试题:final,finalize,finally的区别?
final:关键字
finally:抛出异常后的操作
finalize:它是Object类的方法,由GC来调用,用于配置系统资源或执行其他清除。
这个方法每一个对象只会被调用一次。我们可以在finalize方法中,让对象复活。
即在finalize方法中让一个有效的引用指向了当前准备被回收的垃圾对象时,对象就复活了。
当这个对象再次称为垃圾后,就不会再调用finalize方法。
5.了解finalize的调用情况
public class TestFinalize {
public static void main(String[] args) {
for (int i=1; i<=10; i++){
Example e = new Example(); //每一次循环 e都是新的变量,上一次创建的对象,就称为垃圾对象
}
//正常情况下,GC要等到内存吃紧的时候 或系统空闲的时候才开始工作。
//我们要让GC来工作一下。
System.gc(); //呼唤GC工作一下
//为了让大家看到更多的对象被回收,我让main线程先睡一会,不要着急结束,因为main结束了JVM就关了。
try {
Thread.sleep(10000);//睡10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Example{
@Override
protected void finalize() throws Throwable {
System.out.println("轻轻的我走了,正如我轻轻的来,不带走一段代码!");
}
}
Object方法之 equals()
1.public boolean equals(Object obj):
指示其他某个对象(实参传入对象)是否与此对象(调用equals方法的对象)“相等”。
修饰符:public 任意位置可见
没有static,必须通过“对象."才能访问
没有final,说明子类可以重写
没有native,在源码中可以看到它的方法体实现
返回值类型:boolean 结果只有两种true,false
形参列表: (Object obj) 子类重写equals方法时,形参类型不能修改
Object类型的形参表示可以接收任意类型的实参对象。
即当前对象 可以 和任意类型的对象做equals比较。即比较时,要考虑对象的类型。
2.怎样工作的
默认的equals的方法体实现: return (this == obj); 即默认equals方法也是比较两个对象地址。
比较当前对象 和 其他指定对象的地址。
例如:p1.equals(p2) p1是this,p2是obj
3,能否重写
子类可以选择进行重写。如何重写?idea有模板 Alt + Insert
4.重写的原则
如果不用模板,手动重写equals,那么需要遵循一些原则:
自反性:x.equals(x)一定要返回true
对称性:x.equals(y) 如果返回true,那么要求y.equals(x)也一定返回true
传递性:x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也一定返回true
一致性:x.equals(y)如果返回true,只要下次调用equals时,x和y的对象没有修改,那么也还是返回true,一个非空对象与null做equals比较一定是返回false
public class TestEquals {
public static void main(String[] args) {
Person p1 = new Person("110123199501011234","张三");
Person p2 = new Person("110123199501011234","张三");
System.out.println(p1 == p2);//false ==比较的是对象的内存地址
System.out.println(p1.equals(p2));//重写之前:false
System.out.println(p1.equals(p1));//自己和自己比较,内存地址一样,直接返回true
System.out.println(p1.equals(null));//直接返回false 因为p1如果也为null,那么这个代码就不能执行,报NullPointerException空指针异常
//如果能执行equals方法,p1一定是非空的,非空与null肯定是false
System.out.println("-------------------");
String str1 = "hello";
String str2 = "hello";
String str3 = new String("hello");
System.out.println(str1 == str2);//"hello" 和 "hello"都是常量对象,是同一个,内存地址是一样
System.out.println(str1.equals(str2));
System.out.println(str1 == str3);
System.out.println(str1.equals(str3));
}
}
class Person{
private String id;
private String name;
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true; //比较内存地址,如果两个对象的内存地址一样,就直接返回true
if (o == null || getClass() != o.getClass()) return false;
//||短路或,如果左边的是true。右边就不看。
//o == null 直接返回false
//getClass() != o.getClass() :左边 getClass()表示获取的当前对象的运行时类型
//右边o.getClass() 获取的是实参对象的运行时类型
//类型如果不相等,就返回false
Person person = (Person) o; //向下转型 ,因为方法的形参是Object,在实参传给形参o时,发生了向上转型
//按理说, Person类的equals方法是用于两个Person对象做比较,形参应该(Person p)更合适
//但是equals是重写Object的方法,所以形参列表补不能修改
//这里必须向下转型,因为我们想要比较对象的属性
//如果o按照Object类型处理,就无法访问 Person类的属性了。
return Objects.equals(id, person.id) &&
Objects.equals(name, person.name);
//使用java.util.Objects工具类的equals方法,比较两个对象的id和name是否一样
//&&,表示必须id和name一样才会返回true
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
Object方法之 hashCode()
1.public native int hashCode();
public :公共的
native;表示方法体不是Java语言实现的
int:返回值类型是int,表示一个整数
功能:返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。如果对象没有存到哈希结构的容器中的话,那么hashCode没什么太多作用。
2.返回的是什么?
hashCode:是一个整数
数字摘要:用一个数字代表xx详细信息,
例如:身份证号码,代表一个人的基本信息
在Java中hashCode值用来表示一个对象的信息。
hashCode是否是内存地址?
有的虚拟机确实直接用内存地址来作为hashCode值。
但是绝大多数都不是用内存地址来作为hashCode值。
它是用一些散列函数来对对象的实例变量进行计算得到一个int值作为hashCode。
为什么要用散列函数?
因为散列函数目标是每一个不同的输入可以获得一个不同的输出。
希望,如果对象不同,它的hashCode值就不同。
但是实际中,两个不同的对象,hashCode值可能相同。
3.能否重写
子类可以选择进行重写,idea模板 和equals一起重写。
4.为什么要和equals()一起重写呢?
因为Java要求:
①如果两个对象equals相同,要求hashCode值必须相同
②如果两个对象的hashCode值不同,那么这两个对象equals方法一定不同(false)
③如果两个对象的hashCode值一样,那么这两个对象equals方法可能是true,false
枚举
1.什么是枚举?
所谓的枚举就是一种特殊的类,特殊在于这种类的对象个数是固定的有限的几个。
在实际开发中,会有要求:
例如:星期类、月份类.......
2.如何实现枚举?
jdk1.5之前:
考虑:如何限制对象的个数?
(1)构造器私有化;
(2)在枚举类的内部,提前创建好n个对象供外边使用;
(3)创建的对象应该是个成员变量(类中方法外).为了不创建对象也能调用成员变量,需要设置为静态变量.同时,为了保证对象不被篡改,用final修饰.
public static final Week MONDAY = new Week();
jdk1.5之后:
可以简化声明枚举类型,新增关键字enum,可以使用它来声明枚举.
语法:
修饰符 enum 枚举类型名{
常量对象列表;
其他成员;
}
注意:
(1)如果常量对象列表后面还有其他成员,需要加;结束
(2)枚举类中常量对象列表必须在首行
(3)枚举类型如果没有声明构造器,也会有一个默认的无参构造
(4)枚举的构造器无论如何只能是private的,不用声明也会默认是private
(5)枚举类型可以声明多个构造器,当然多个构造器也都是private
通过调用无参构造创建对象时,()可以省略
通过调用有参构造创建对象时,在常量对象名后面直接加(实参列表),参数类型和个数一定要对应.
(6)语法上枚举类的其他成员变量没有要求必须时final的,但是习惯上也会声明为final,但这个时候,就必须要为这个参数赋值,否则会报错.
(7)用enum声明的枚举类型,默认会继承一个枚举公共父类,java.lang.Enum类,根父类仍然时Object类.这意味着使用enum声明的枚举类不能再继承别的类了.
(8)因为Enum类中重写了Object类中的toString()等方法.
toString返回的是常量对象的名称,当然如果需要的话,还可以再次重写.
(9)从父类Enum中还继承了别的新的方法
String name():用来返回枚举常量对象的名称;
int ordinal():用来返回枚举常量对象的索引,索引下标从0开始;
枚举类型名[] values:API中没有的一个方法,用来获取枚举常量中的所有对象.
(10)从jdk1.5之后,switch的case后可以直接写枚举类型的常量对象.(直接写就行,不用带引号或别的符号)
public enum Month {
JANUARY(1,"一月"),//等价于调用有参构造器生成一个对象.
FEBRUARY(2,"二月"),
MARCH(3,"三月"),
APRIL(4,"四月"),
MAY(5,"五月"),
JUNE(6,"六月"),
JULY(7,"七月"),
AUGUST(8,"八月"),
SEPTEMBER(9,"九月"),
OCTOBER(10,"十月"),
NOVEMBER(11,"十一月"),
DECEMBER(12,"十二月");
private final int num ;
private String description;
public int getNum() {
return num;
}
Month(int num, String description) {
this.num = num;
this.description = description;
}
public static Month getByValue(int value){
for (int i = 0; i < Month.values().length; i++) {
if(Month.values()[i].num == value){
return Month.values()[i];
}
}
return null;
}
@Override
public String toString() {
return num+"->"+super.toString()+"->"+description;
}
}
包装类
1.什么是包装类
包装类是为了解决Java的byte,short等八种基本数据类型和void这些类型不属于面向对象范畴的问题提出来的概念.
Java为这些类型提供了对应的引用数据类型
byte --> Byte
short --> Short
int --> Integer
float --> Float
double --> Double
char --> Character
boolean --> Boolean
void --> Void
2.为什么要有包装类?
八种基本数据类型和void这些类型不属于面向对象,导致我们后面很多为对象而设计的API,这些类型都不能使用.Java很多新的语法是为面向对象设计的,例如:泛型等,这些类型也不支持.
为了解决这些兼容的问题,增加了包装类
为什么Java一开始不做"纯"面向对象语言?
因为Java发明的时候,受到了C语言的影响,C语言中是有这些类型的,并且有好处:(1)有很丰富的运算符(指令)支持;(2)不同的平台有统一的宽度标准.
3.这些包装类都在java.lang包中
4.关于包装类的操作:装箱和拆箱
装箱:把基本数据类型--->包装为---->包装类的对象
拆箱:把包装类的对象--->拆解为--->基本数据类型
jdk1.5之前:
只能手动装箱和拆箱.
jdk1.5之后:
支持自动装箱和自动拆箱.
但是自动装箱和拆箱只能发生在对应的类型之间 如 int <---->Integer
int b = 1;
Integer obj1 = b;//自动装箱
Double obj2 = new Double(1.0);
double d = obj2;//自动拆箱
5.API(部分)
(1)获取每一种类型的最大值和最小值(包装类.MAX_VALUE,包装类.MIN_VALUE)
//Integer.MAX_VALUE
//Integer.MIN_VALUE
(2)获取某个十进制的二进制形式,八进制形式等.
包装类.toBinaryString(十进制整数):转为二进制表达形式.
包装类.toOctalString(十进制整数):转为八进制
包装类.toHexString(十进制整数):转为十六进制
(3)大小写转化
Character.toUpperCase(char类型的变量):转大写
Character.toLowerCase(char类型的变量):转小写
(4)比较两个小数的大小
尽量不要使用'==',推荐使用:
Double.compare(参数1,参数2);它的结果是一个整数,0代表相等,1代表前一个参数比后一个参数大,-1表示前一个参数比后一个参数小.
(5)支持各种基本数据类型与字符串之间的转换(使用频率很高)
Integer.parseInt(字符串)返回的是int值
Double.parseInt(字符串)返回的是Double值
Boolean.parseInt(字符串)返回的是Boolean值
Long.parseInt(字符串)返回的是Long值
对象名.charAt(下标,从零开始)返回char类型
6.包装类对象的缓存
Byte -128~127
Short -128~127
Integer -128~127
Long -128~127
Float,Double没有缓存对象(小数,使用频率高的值范围很大,存起来很占内存)
Boolean:true,flase
Character: 0~127 正好是早期ASCII表中的字符.
这个范围的值使用频率很高,包装类对象的值在这个范围内的话,且没有主动使用new创建对象的话,那么是指向常量池中的缓存对象.
public class TestCache {
public static void main(String[] args) {
int a = 1;
int b = 1;
System.out.println(a == b);//true 比较数据值
Integer i1 = 1; //右边的1自动装箱为Integer的对象
Integer i2 = 1;//i1和i2指向了同一个缓存对象
System.out.println(i1 == i2);//true 比较地址值
int c = 128;
int d = 128;
System.out.println(c == d);//true 比较数据值
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);//false 比较地址值
Integer i5 = new Integer(1);
Integer i6 = new Integer(1);
System.out.println(i5 == i6);//false
Integer i7 = Integer.valueOf(1);
Integer i8 = Integer.valueOf(1);
System.out.println(i7 == i8);//true
}
}
7.补充
当包装类与基本数据类型进行运算时,会自动拆箱为基本数据类型再进行比较;如果是两个基本数据类型,那么还会进行自动类型转换,变成同一种数据类型后比较.
public class TestExam {
public static void main(String[] args) {
Integer i = 1000;
int j = 1000;
System.out.println(i==j);//true
System.out.println("----------------------");
Integer m = 1000;
double n = 1000;
System.out.println(m == n);//true (1)m先自动拆箱为int(2)int再自动类型提升为double
System.out.println("---------------------------");
Double d1 = 1.0;
double d2 = 1.0;
System.out.println(d1 == d2);//true d1自动拆箱为double
}
}
包装类一种特殊的引用数据类型,它的对象是不可变对象,一旦修改就会产生新对象。有相同性质的还有String类.
public class TestExam2 {
public static void main(String[] args) {
int num = 1;
Integer obj = 1;
Data data = new Data();
data.x = 1;
change(num, obj, data);
System.out.println("num = " + num);//1
System.out.println("obj = " + obj);//1
System.out.println("data.x = " + data.x);//11
}
/*
形参a是基本数据类型,它的修改和实参无关
形参Data d ,是引用数据类型,它对成员变量的修改和实参有关。
Integer b,是引用数据类型,Integer是特殊的引用数据类型,它的对象是不可变对象,一旦修改就会产生新对象。
b+=10,会使得b指向新的对象,就和实参obj无关了
*/
public static void change(int a , Integer b, Data d ){
a += 10;
b += 10;
d.x += 10;
}
}
class Data{
int x;
}