java面向对象
1,嵌套类(内部类)nested class(inner class)
java内部类与c++嵌套类最大的不同就在于是否有指向外部的引用。
创建一个static内部类的对象,不需要一个外部类对象,不能从一个static内部类的一个对象访问一个外部类对象。
内部类或嵌套类在类层级上没有限制,内部类可以是私有类。
2,集合类
java中的容器类库一共有两种主要类型:Collection和Map
collection的每个槽内只保存一个元素;map的每个槽内保存的是键值对
collection包括list,set,Queue,
list:Arraylist,LinkedList,Vector;
list:将以特定次序存储元素。ArrayList:擅长随机访问,擅长插入、删除和移动。
vector:向量类(vector) 实现类似动态数组的功能。创建了一个向量类的对象后,可以往其中随意插入不同类的对象,即不需顾及类型也不需预先选定向量的容量,并可以方便地进行查找。对于预先不知或者不愿预先定义数组大小,并且需要频繁地进行查找,插入,删除工作的情况。可以考虑使用向量类。
set:Hashset,Treeset,linkedHashset;
set:不含重复的元素。Hashset:使用散列函数;TreeSet:使用红黑树。linkedHashset:使用链表结构的散列函数。
queue:Priorityqueue;
queue:先进先出的容器。
Map包括HashMap,HashTable,TreeMap
java中的map都不在重复的key。
hashmap:线程不安全,适合在map中插入、删除和定位元素。没有分类或者排序,它允许一个null键和多个null值。
hashtable:线程安全。安全一般意味着效率低,同步。它不允许null键和null值。
Treemap:适合按自然顺序或自定义顺序遍历键,通常比Hashmap速度慢,在需要排序的map时才使用。
3,构造函数和析构函数
重写(覆盖),重载
覆盖又叫重写,因此重写和覆盖是一个概念。它是覆盖了一个方法并且对其重写,以求达到不同的作用。形式有:对接口方法的实现,在继承中也可能会在子类覆盖父类中的方法。
重载:它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,VM就会根据不同的参数样式,来选择合适的方法执行。
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Parent{ //定一个类 public void read(){ } public void show(){ //重载了show方法 } public void show( int a){ //重载了show方法,比第一个方法多了一个参数 } } public class Son extends Parent{ //son类继承父类parent public void read(){ //覆盖了父类的read方法。 } } |
重载构造方法是可以的。
但是重写则不可以,因为被重写的前提是被继承,而构造方法根本就不能被继承,所以谈不上被重写。
构造函数的格式
了解了构造函数的基本概念,现在来写一个构造函数,希望大家可以了解、记忆其格式,通过实例发现其与普通函数的不同之处。
01 public class Demo{
02 private int num=0;
03 //无参构造函数
04 Demo()
05 {
06 System.out.println("constractor_run");
07 }
08 //有参构造函数
09 Demo(int num)
10 {
11 System.out.println("constractor_args_run");
12 }
13 //普通成员函数
14 public void demoFunction()
15 {
16 System.out.println("function_run");
17 }
18 }
构造函数与普通函数的区别
下面来详细的分析下构造函数与普通函数的区别,通过两者的一个对比,希望可以加深对构造函数的概念的理解。
1) 格式不同:
构造函数不存在返回类型,函数名与所在类的类名一致;
普通函数有返回类型,函数名可以根据需求进行命名。
2)调用时期不同
构造函数在类的对象创建时就运行;
普通函数在对象调用时才会执行。
3)执行次数不同
一个对象创建后,其构造函数只执行一次,就是创建时执行;
一个对象创建后,其普通函数可以执行多次,取决于对象的调用次数。
java派生类被构造时一定先调用父类的构造函数
子类可以通过super关键字来显式地调用父类的构造函数。
当父类没有提供无参数的构造函数时,子类的构造函数中必须显式的调用父类的构造函数;
如果父类提供了无参数的构造函数,此时子类的构造函数就可以不显式的调用父类的构造函数,默认调用父类的无参构造函数。
- package com.bjut.StudyTest;
- class Person {
- public Person() {
- System.out.println("Base has no args.");
- }
- public Person(String temp) {
- System.out.println("Base:" + temp);
- }
- }
- class Student extends Person {
- public Student() {
- super("a");
- System.out.println("Student has no args.");
- }
- public Student(String temp) {
- System.out.println("Student:" + temp);
- }
- }
- public class TestConstruction {
- public static void main(String[] args) {
- Person p = new Student(); // 先调用父类的构造函数,显示调用指定的父类构造函数。
- Student s = new Student("b"); // 先调用父类的构造函数,默认调用父类无参构造函数。
- }
- }
输出:
- Base:a
- Student has no args.
- Base has no args.
- Student:b
当有父类时,在实例化对象时会先执行父类的构造函数,然后执行子类的构造函数。
(补充)java 程序初始化工作执行的顺序:
父类静态变量 -> 父类静态代码块 -> 子类静态变量 -> 子类静态代码块
-> 父类非静态变量 -> 父类非静态代码块 -> 父类构造函数
-> 子类非静态代码块 -> 子类非静态变量 -> 子类构造函数
注意:constructor在一个对象被new时执行。
public class Z extends X{//步骤1:先调用X,再调用Z。
Y y=new Y();//步骤2:接着构造Y
Z(){//步骤3:最后构造自身Z
System.out.print("z");
}
public static void main(String[] args){
new Z();
}
}
析构函数在java中常见的就是finalize()函数,GC垃圾回收机制,需要程序员进行重写。
浅拷贝,即在定义一个类A,使用类似A obj; A obj1(obj);或者A obj1 = obj; 时候,由于没有自定义拷贝构造函数,C++编译器自动会产生一个默认的拷贝构造函数。这个默认的拷贝构造函数采用的是“位拷贝”(浅拷贝),而非“值拷贝”(深拷贝)的方式,如果类中含有指针变量,默认的拷贝构造函数必定出错。
用一句简单的话来说就是浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
多态
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)
实现多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
多态的作用:消除类型之间的耦合关系。
多态有两种表现形式:重载和覆盖
java中的动态绑定和静态绑定(或者叫做前期绑定和后期绑定)
我们发现java属于后期绑定。在java中,几乎所有的方法都是后期绑定的,在运行时动态绑定方法属于子类还是基类。但是也有特殊,针对static方法和final方法由于不能被继承,因此在编译时就可以确定他们的值,他们是属于前期绑定的。特别说明的一点是,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的(由此我们也可以知道:将方法声明为final类型的一是为了防止方法被覆盖,二是为了有效的关闭java中的动态绑定)。java中的后期绑定是有JVM来实现的,我们不用去显式的声明它,而C++则不同,必须明确的声明某个方法具备后期绑定。
java当中的向上转型或者说多态是借助于动态绑定实现的,所以理解了动态绑定,也就搞定了向上转型和多态。
对于java当中的方法而言,除了final,static,private和构造方法是前期绑定外,其他的方法全部为动态绑定。而动态绑定的典型发生在父类和子类的转换声明之下:
比如:Parent p = new Children();
与方法不同,在处理java类中的成员变量(实例变量和类变量)时,并不是采用运行时绑定,而是一般意义上的静态绑定。所以在向上转型的情况下,对象的方法可以找到子类,而对象的属性(成员变量)还是父类的属性(子类对父类成员变量的隐藏)。
Java代码
[java] view plain copy
- public class Father {
- protected String name = "父亲属性";
- }
- public class Son extends Father {
- protected String name = "儿子属性";
- public static void main(String[] args) {
- 10. Father sample = new Son();
- 11. System.out.println("调用的属性:" + sample.name);
- 12. }
13. }
结论,调用的成员为父亲的属性。
这个结果表明,子类的对象(由父类的引用handle)调用到的是父类的成员变量。所以必须明确,运行时(动态)绑定针对的范畴只是对象的方法。
现在试图调用子类的成员变量name,该怎么做?最简单的办法是将该成员变量封装成方法getter形式。
代码如下:
Java代码
[java] view plain copy
- public class Father {
- protected String name = "父亲属性";
- public String getName() {
- return name;
- }
- }
- public class Son extends Father {
- 10. protected String name = "儿子属性";
- 11.
- 12. public String getName() {
- 13. return name;
- 14. }
- 15.
- 16. public static void main(String[] args) {
- 17. Father sample = new Son();
- 18. System.out.println("调用的属性:" + sample.getName());
- 19. }
20. }
结果:调用的是儿子的属性
java因为什么对属性要采取静态的绑定方法。这是因为静态绑定是有很多的好处,它可以让我们在编译期就发现程序中的错误,而不是在运行期。这样就可以提高程序的运行效率!而对方法采取动态绑定是为了实现多态,多态是java的一大特色。多态也是面向对象的关键技术之一,所以java是以效率为代价来实现多态这是很值得的