先看一个例子:
public class person { string name; char gender; person(string name,char gender){ this.name = name; this.gender = gender; } } public class student extends person { double score; student(string name,char gender,double score){ super(name,gender); //调用父类有参构造 this.score = score; super.name = "tom"; } public static void main(string[] args) { person p = new student("tom",'男',80); //向上转型 p.score = 100; //编译错误,java编译器会根据引用的类型(person),而不是对象的类型(student)来检查调用的方法是否匹配。
//向上转型即基类引用指向子类,基类引用可以指向子类的对象,但通过父类的引用只能访问父类所定义的成员,不能访问子类扩展的部分
system.out.println(p instanceof person); //true
system.out.println(p instanceof student); //true
} }
(1)object类位于java.lang包中,java.lang包包含着java最基础和核心的类,在编译时会自动导入;
(2)object类是所有java类的祖先。每个类都使用 object 作为超类。所有对象(包括数组)都实现这个类的方法。可以使用类型为object的变量指向任意类型的对象
object类是所有java类的根基类
如果在类的声明中未使用extends关键字致命其基类,则默认基类为object类,也就是说
public class person {…}等价于public classperson extends object {…}
object类提供的方法:
java的任何类都继承了这些函数,并且可以覆盖不被final
修饰的函数。例如,没有final
修饰的tostring()
函数可以被覆盖,但是final wait()
函数就不行。主要用的方法有tostring和equals方法
//测试类 public class test { public static void main(string[] args) { dog d = new dog(); system.out.println("d: "+d); //等价于 system.out.println("d: "+d.tostring()); } } class dog { } //输出结果: //d: test.dog@2a139a55 //d: test.dog@2a139a55
输出的结果就是类的对象的相关信息(即内存地址)
如果对于object中某些方法不满意可以自己在自己类中进行重写,例如:
public class test { public static void main(string[] args) { dog d = new dog(); system.out.println("d: "+d); //等价于 system.out.println("d: "+d.tostring()); } } class dog { public string tostring(){ return "dog"; } } //输出结果 d: dog d: dog
object类中定义了public boolean equals(object obj)方法
提供定义对象是否相等的逻辑(比较的是在堆内存中的内存地址),使用格式:
x.equals(y)当x和y是同一个对象的应用时返回true,斗则返回false
注意:jdk提供的一些类,如string,date等,重写了object的equals方法,调用这些类的equals方法,x.equals(y),当x和y所引用的对象是同一类对象且属性值相等时(并不一定是相同对象),返回true,否则返回false (重写了object的equals方法的封装,比如string类对象,他们使用equals方法比较的就是属性值即成员变量的值)
可以根据需要在用户自定义类型中重写equals方法
public class test { public static void main(string[] args) { dog d1 = new dog(); dog d2 = new dog(); system.out.println(d1 == d2); system.out.println(d1.equals(d2)); } } class dog { } //输出结果: //false //false
public class test { public static void main(string[] args) { dog d1 = new dog(); dog d2 = new dog(); system.out.println(d1 == d2); system.out.println(d1.equals(d2)); } } class dog { public boolean equals(object obj) { return true; } } //输出结果: //false //true
从上面两个例子可以看出系统方法也是可以重写的,同时可以看出“==”这个运算符,如果是引用类型比较的就是堆内存中的内存地址,如果是基本数据类型比较的是值(基本数据类型的局部变量存放在栈中),创建出来的对象的成员变量存放在堆内存的常量池中
public
native
int
hashcode();
hash值:java中的hashcode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。
情景:考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)。
大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashcode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashcode方法,得到对应的hashcode值。实际上在hashmap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
重写hashcode()方法的基本规则:
object本地实现的hashcode()方法计算的值是底层代码的实现,采用多种计算参数,返回的并不一定是对象的(虚拟)内存地址,具体取决于运行时库和jvm的具体实现。
public boolean equals(object obj) { return (this == obj); }
java的动态绑定又称为运行时绑定。动态绑定是指“在执行期间(而非编译其间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法”
//新建一个基类(动物类) public class animal { public string name; animal(string name) { this.name = name; } public void eat() { system.out.println("吃...."); } }
//新建一个子类cat继承animal基类 public class cat extends animal { cat(string n) { super(n); } public void eat() { system.out.println("猫吃鱼..."); } }
//新建一个子类dog继承基类animal public class dog extends animal{ dog(string n) { super(n); } public void eat() { system.out.println("狗吃屎..."); } }
//测试类 public class test { public static void main(string args[]) { animal c = new cat("catname"); animal d = new dog("dogname"); c.eat(); d.eat(); } } //输出结果: //猫吃鱼..... //狗吃屎.....
上面的例子中,根据animal对象的引用的不同的实际类型而调用相应的eat方法
animal c = new cat("catname");
animal d = new dog("dogname");
上面两行代码的内存分配图如下:
eat方法有三个,分别是animal的,dog的和cat的,动态绑定就是调用的实际new的那个方法,指向的就是那个方法。也就是c指向的实际方法应该是cat类重写的那个eat方法。在上面的对象转型中我们也讲过父类引用指向子类,但是不能访问子类新增成员,子类中重写父类方法则父类方法被覆盖,调用的就是被重写后的方法
当后期需要扩展的时候,只需要加一个扩展类,然后在test类中new一个扩展对象出来并使用对象.方法的方式引用即可,扩展性非常强
动态绑定有三个必要条件:1.要有继承 2.要有重写 3.父类引用指向子类对象(向上转型)
1.使用abstract关键字来修饰一个类的时候,这个类叫做抽象类;同样的,使用abstract来修饰一个方法时,这个方法叫做抽象方法。
2.含有抽象方法的类必需被声明为抽象类,抽象类必需被继承,抽象方法必需被重写。
3.抽象类不能被实例化,通过继承,然后向上转型来引用
4.抽象方法只需声明,而不需要实现
5.抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public;
看下面这个例子:
abstract class a{//定义一个抽象类 public void fun(){//普通方法 system.out.println("存在方法体的方法"); } public abstract void print();//抽象方法,没有方法体,有abstract关键字做修饰,方法只需要被声明不需要实现,因为抽象方法必须被重写,所以没必要实现抽象方法 }
public class b extends a{ public static void main(string[] args){ a a = new b(); //向上转型,父类引用指向子类 a.print(); } public void print(){ system.out.println("子类重写父类的抽象方法"); } } //输出结果: //子类重写父类的抽象方法
final修饰的变量的值不能够被改变
final修饰的方法不能够被重写
final修饰的类不能够被继承
public class demo { public static void main(string[] args) { t t = new t(); t.i = 9; //编译出错,因为变量i被final修饰,不能被修改值 t.j = 10; //不会报错,因为变量j没有被final修饰 } } class t { final int i = 0; int j = 0; }
接口(英文:interface),在java编程语言中是一个抽象类型,是抽象方法和常量值的定义的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。本质上讲,接口是一种特殊的抽象类,这种抽象类中值包含常量和方法的定义,而没有变量和方法的实现
除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
接口定义举例:
public interface runner { public static final int id = 1; public void start(); public void run(); public void stop(); }
接口特性:
定义java类的语法格式:
< modifier > class < name > [ extends < superclass > ] [ implements < interface > [ , <interface> ] * ] { < declarations > * }
示例:
interface singger{ public void sing(); } class test implements singger{//实现singger接口 public static void main(string[] args){ test t = new test(); t.sing(); } public void sing() { system.out.println("student is singing..."); } } //输出结果: //student is singing...
如对本文有疑问, 点击进行留言回复!!
SpringBoot引用阿里easyexcel,Excel导出返回浏览器下载
HashMap、Hashtable、ConcurrentHashMap三者间的异同
解决RecycleView 中Item包含Edittext时,滑动view复用导致数据错乱的问题
多线程、同步工作原理、死锁案例、Lock接口、线程的生命周期的讲解及实现
网友评论