java类和对象

一、面向对象三大特征
面向对象的三个基本特征是:封装、继承、多态。
1、封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系。
为什么需要封装?封装符合面向对象设计原则的第一条:单一性原则,一个类把自己该做的事情封装起来,而不是暴露给其他类去处理,当内部的逻辑发生变化时,外部调用不用因此而修改,他们只调用开放的接口,而不用去关心内部的实现。
2、继承的定义: 它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 继承现有类 + 扩展。通过继承创建的新类称为“子类”或“派生类”。 被继承的类称为“基类”、“父类”或“超类”。 继承的过程,就是从一般到特殊的过程。
为什么需要继承:代码重用是一点,最重要的还是所谓想上转型,即父类的引用变量可以指向子类对象,这是Java面向对象最重要特性多态的基础。
Java的类可以分为三类:
  • 类:使用class定义,没有抽象方法
  • 抽象类:使用abstract class定义,可以有也可以没有抽象方法
  • 接口:使用interface定义,只能有抽象方法

在这三个类型之间存在如下关系:
  • 类可以extends:类、抽象类(必须实现所有抽象方法),但只能extends一个,可以implements多个接口(必须实现所有接口方法)
  • 抽象类可以extends:类,抽象类(可全部、部分、或者完全不实现父类抽象方法),可以implements多个接口(可全部、部分、或者完全不实现接口方法)
  • 接口只能extends:一个接口

继承以后子类可以得到什么:
  • 子类拥有父类非private的属性和方法
  • 子类可以添加自己的方法和属性,即对父类进行扩展
  • 子类可以重新定义父类的方法,即多态里面的覆盖,后面会详述

关于构造函数:
  • 构造函数不能被继承,子类可以通过super()显示调用父类的构造函数
  • 创建子类时,编译器会自动调用父类的 无参构造函数如果父类没有定义无参构造函数,子类必须在构造函数的第一行代码使用super()显示调用
  • 类默认拥有无参构造函数,如果定义了其他有参构造函数,则无参函数失效,所以父类没有定义无参构造函数,不是指父类没有写无参构造函数。
3. 多态
多态定义:多态可以说是“一个接口,多种实现”或者说是父类的引用变量可以指向子类的实例,被引用对象的类型决定调用谁的方法,但这个方法必须在父类中定义。
多态可以分为两种类型:编译时多态(方法的重载)和运行时多态(继承时方法的重写),编译时多态很好理解,多态运行时多态依赖于继承、重写和向上转型。
一个方法可以由:修饰符如public、static+返回值+方法名+参数+throw的异常 5部分构成。其中只有方法名和参数是唯一性标识,意即只要方法名和参数相同那他们就是相同的方法。所谓参数相同,是指参数的个数,类型,顺序一致,其中任何一项不同都是不同的方法
何谓重载(同名不同参):重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序。如果仅仅是修饰符、返回值、throw的异常 不同,那这是2个相同的方法,编译都通不过,更不要说重载了。
何谓覆盖/重写(同返回值同名同参):覆盖描述存在继承关系时子类的一种行为子类中存在和父类相同的方法即为覆盖,何谓相同方法请牢记前面的描述,方法名和参数相同,包括参数个数、类型、顺序。
覆盖/重写的规则:
  • 子类不能覆盖父类private的方法,private对子类不可见,如果子类定义了一个和父类private方法相同的方法,实为新增方法;
  • 重写方法的修饰符一定要大于被重写方法的修饰符(public > protected > default > private);
  • 重写抛出的异常需与父类相同或是父类异常的子类,或者重写方法干脆不写throws;
  • 重写方法的返回值必须与被重写方法一致,否则编译报错;
  • 静态方法不能被重写为非静态方法,否则编译出错。
二、对象的主要特征
对象标识(唯一身份,每个标识永远不同)、对象行为(对对象施加哪些方法,通过可调用的方法定义)、对象状态(对象如何反应,用来描述当前特征信息)。
三、类之间的关系
  • 依赖:一个类操作另一个类的对象,就说一个类依赖于另一个类。应该尽可能地将相互依赖的类减少最小,就是让类之间的耦合度最小。
  • 聚合:一个类的对象包含另一个类的对象。
  • 继承:表示一般到特殊的关系。
四、预定义类
  • 构造器:构造并初始化对象,构造器的名字应该与类名相同。用 new 构造函数(参数...)。
  • 访问器:只访问对象而不修改对象的方法有时称为访问器。
  • 修改器:访问对象时会修改对象状态。
当构造器构造一个对象后,可以将这个对象传递给一个方法,或者将一个方法应用于刚刚创建的对象。如果希望构造的对象可以多次使用,因此,需要将对象存放在一个变量中: Date br=new Date();。可以将一个有引用对象的变量赋值给该类型的变量,这两个变量则同时引用该对象:LocalDate dt=LocalDate.now(); LocalDate dt1;dt1=dt;
一个对象变量并没有实际包含一个变量,仅仅引用一个对象。可以显示将一个对象变量设置成null,表明这个对象变量目前没有引用任何对象。局部变量不会自动初始化为null,必须要通过调用new或将它们设置为null进行初始化。
LocalDate本地日期:j ava8之前,Date类都是可变类。当我们在多线程环境下使用它,编程人员应该确认Date对象的线程安全。Java8的Date和Time API提供了线程安全的不可变类。编程人员不用考虑并发的问题。
格林尼治标准时间(GMT,旧译“格林威治平均时间”或“格林威治标准时间”)是指位于伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。 理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能和实际的太阳时相差16分钟。 地球每天的自转是有些不规则的,而且正在缓慢减速。所以,格林尼治时间已经不再被作为标准时间使用。
现在的标准时间——协调世界时(UTC)——由原子钟提供。 自1924年2月5日开始,格林尼治天文台每隔一小时会向全世界发放调时信息。
计算机类的时间是用距离一个固定时间点的毫秒数表示,这个点就是所谓的纪元,它是UTC时间1970年1月1日 00:00:00。
java.time.LocalDate: LocalDate只提供日期不提供时间信息。它是不可变类且线程安全的。
java.time.LocalTime:LocalTime只提供时间而不提供日期信息,它是不可变类且线程安全的。
java.time.LocalDateTime:LocalDateTime提供时间和日期的信息,它是不可变类且线程安全的。
java.time.Year:Year提供年的信息,它是不可变类且线程安全的。
java.time.Duration:Duration是用来计算两个给定的日期之间包含多少秒,多少毫秒,它是不可变类且线程安全的。
java.time.Period:Period是用来计算两个给定的日期之间包含多少天,多少月或者多少年,它是不可变类且线程安全的。
Date转LocalDate:
Date date = new Date();
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate 转 Date:
LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.toInstant(ZoneOffset.UTC))
格式化日期时间: import java.time.format.DateTimeFormatter;
DateTimeFormatter ymd = DateTimeFormatter.ofPattern("yyyy-MM-dd");

//字符串转换成LocalDate类型
LocalDate ld = LocalDate.parse("2015-11-23", ymd);
System.out.println("年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());
System.out.println("从1970年1月1日开始的总天数:"+ld.toEpochDay());

//数字转化成LocalDate类型
ld = ld.plusDays(1);
System.out.println("加一天年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());

//利用LocalDate类型计算
ld = ld.plusDays(1);
System.out.println("加一天年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());

ld = ld.minusDays(2);
System.out.println("减两天年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());

ld = ld.plusMonths(1);
System.out.println("加一个月年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());

ld = ld.minusMonths(1);
System.out.println("减一个月年月日:"+ld.getYear()+"-"+ld.getMonthValue()+"-"+ld.getDayOfMonth());

五、自定义类
自定义类中用于操作对象以及存取它们的实例域,每个方法存在两种参数。第一种参数称为隐式参数,是出现在方法名前的实例域,通常用this表示;第二种参数称为显式参数,是通过方法名后面参数列表传递。
1)封装具备条件: 一个私有的数据域;一个公有的域访问器;一个公有的域更改器。封装可以改变内部实现,除了该类的方法外,不会影响其他代码。更改器可以执行错误检验,然后直接赋值将不会进行这些处理。
2)final关键字: 1.final关键字提高了性能。JVM和Java应用都会缓存final变量。2.final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。3.使用final关键字,JVM会对方法、变量及类进行优化。创建不可变类要使用final关键字,不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。可以将类中的实例域定义成final,构建对象时必须初始化这样的域。也就是说,必须确保在每个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。用final修饰符大多数应用于基本类型域和不可变类的域。
3)静态域和静态方法: Java 中被static 修饰的域或方法常被称作静态的,它属于类但是不属于任何独立的对象。就算这个类没有任何实例化对象,静态的也存在。
1、静态域:如果将静态域定义为static ,那么每个类中只有一个这样的域,而每一个对象对于所有的实例域都有自己的一份拷贝本。也就是说,非静态域是对象独有,但是静态域属于所有该类实例化后的对象公有。
2、静态常量:静态变量使用的比较少,但是静态常量却使用的比较多(在实际的开发中,静态常量可以做到:该改一处而改全处作用,大大的减少修改和出错的地方),例如在Java中的Math类中定义了一个圆周率的静态常量:
public static final double PI = 3.14159265358979323846;  
3、静态方法:使用static修饰的静态方法是属于整个类的类方法,它在内存中的代码段会随类的定义而被分配和装载;而非静态方法是属于具体对象的方法,当这个对 象创建时,在对象的内存中会拥有此方法的专用代码段。静态方法是一种不能由对象操作的方法, 例如Math类中的求取一个数的次方的pow()方法就是一个静态方法。表达式是: Math.pow(x, a),在运算时,不使用任何Math对象,换句话说,没有隐式的参数。可以认为静态方法是没有this参数的方法(在一个非静态的方法中,this参数表示这个方法的隐式参数)。Student 类的静态方法不能访问Id实例域,因为Id它不是静态的,Student的静态方法它不能操作对象,但是静态方法可以访问自身类中的静态域。总之就是常说的:静态方法中只能调用静态的东西,非静态的是不能使用的。
4)main方法: 我们学习java的时候,程序中大多会有一个main 方法,我们都称作程序的入口,main方法不对任何对象进行操作,事实上,在启动程序的时候,还没有任何一个对象,静态的main方法将执行并创建程序所需的对象。在java中,main()方法是java应用程序的入口方法。java虚拟机通过main方法找到需要启动的运行程序,并且检查main函数所在类是否被java虚拟机装载。如果没有装载,那么就装载该类,并且装载所有相关的其他类。因此程序在运行的时候,第一个执行的方法就是main()方法。通常情况下, 如果要运行一个类的方法,必须首先实例化出来这个类的一个对象,然后通过"对象名.方法名()"的方式来运行方法,但是因为main是程序的入口,这时候还没有实例化对象,因此将main方法声明为static的,这样这个方法就可以直接通过“类名.方法名()”的方式来调用。
开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:
5)方法参数: 程序设计语言中,将参数传递给方法(或函数)有两种方法。按值传递(call by value)表示方法接受的是调用者提供的值;按引用调用(call by reference)表示方法接受的是调用者提供的变量地址。Java程序设计语言都是采用按值传递。Java中方法参数的使用情况:(1)一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。(2)一个方法可以改变一个对象(数组)参数的状态。(3)一个方法不能让对象参数(数组)引用一个新的对象。
基本数据类型的传递:
  • percent将值拷贝给x,percent与x的地址值不同;
  • tripleValue()方法将x的值10乘以3后得到10,percent的值不变;
  • tripleValue()弹栈后,参数变量x不再使用。
对象或数组作为参数传递:
  • Employee harry = new Employee("Harry", 50000); 创建了一个对象变量harry,引用了Employee的一个对象;
  • tripleSalary(harry); 将对象harry的地址值传递给参数x, 此时变量harry和x都引用了堆中的同一个Employee对象;并通过方法将这一对象的薪金提高了200%;
  • tripleSalary(harry)方法弹栈后,参数变量x不再使用。对象变量harry继续引用那个薪金增至3倍的对象。
6)对象构造: Java中有5种创建对象的方式,下面给出它们的例子还有它们的字节码。
1.使用new关键字:这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。
Employee emp1 = new Employee();
0: new #19
3: dup
4: invokespecial #21
2.使用Class类的newInstance方法:我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。我们可以通过下面方式调用newInstance方法创建对象:
Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance();
或者
Employee emp2 = Employee.class.newInstance();
51: invokevirtual #70
3.使用Constructor类的newInstance方法:和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。
4.使用clone方法:无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。
Employee emp4 = (Employee) emp3.clone();
5.使用反序列化:当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
我们从上面的字节码片段可以看到,除了第1个方法,其他4个方法全都转变为invokevirtual(创建对象的直接方法),第一个方法转变为两个调用,new和invokespecial(构造函数调用)。
一个对象的构造过程:
1.用类加载器加载父类,按父类静态变量定义的顺序的为父类所有静态变量分配空间,并赋予父类静态变量默认值;
2.用类加载器加载自己,按自己静态变量定义的顺序的为自己所有静态变量分配空间,并赋予自己静态变量默认值;

3.按父类静态变量定义的顺序的为父类所有静态变量赋上定义的值;
4.执行父类静态代码块;

5.按自己静态变量定义的顺序的为自己所有静态变量赋上定义的值;
6.执行自己静态代码块;

7.为父类实例变量分配空间,并赋予默认值;
8.为自己实例变量分配空间,并赋予默认值;

9.按父类实例变量定义的顺序的为父类所有实例变量赋上定义的值;
10.执行父类的构造代码块;
11.执行父类的构造方法;

12.按自己实例变量定义的顺序的为自己所有实例变量赋上定义的值;
13.执行自己的构造代码块;
14.执行自己的构造方法。
7)包
包(package)定义: 是Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。Java中提供的包主要有以下3种用途:1) 将功能相近的类放在同一个包中,可以方便查找与使用。2) 由于在不同包中可以存在同名类,所以使用包在一定程度上可以避免命名冲突。3) 在Java中,某次访问权限是以包为单位的。如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。
J ava 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。
包语句的语法格式为:package pkg1[.pkg2[.pkg3…]];
例如,一个Something.java 文件它的内容
package net.java.util
public class Something{
...
}
那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包) 的作用是把不同的java 程序分类保存,更方便的被其他 java 程序调用。
import 关键字: 为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能。在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为。
import package1[.package2…].(classname|*);
如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。
用 import 关键字引入,使用通配符 "*"
import payroll.*;
使用 import 关键字引入 Employee 类:
import payroll.Employee;
import语句不仅可以导入类,还增加了导入静态方法和静态域的功能。例如,如果在源文件的顶部,添加一条指令:import static java.lang.System.*;
package 的目录结构: 类放在包中会有两种主要的结果:1)包名成为类名的一部分,正如我们前面讨论的一样。2)包名必须与相应的字节码所在的目录结构相吻合。下面是管理你自己 java 中文件的一种简单方式:将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:
// 文件名 : Car.java
package vehicle;
public class Car {
// 类实现
}
接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。
....\vehicle\Car.java
现在,正确的类名和路径将会是如下样子:
  • 类名 -> vehicle.Car
  • 路径名 -> vehicle\Car.java (在 windows 系统中)
通常,一个公司使用它互联网域名的颠倒形式来作为它的包名。例如:互联网域名是 runoob.com,所有的包名都以 com.runoob 开头。包名中的每一个部分对应一个子目录。例如:有一个 com.runoob.test 的包,这个包包含一个叫做 Runoob.java 的源文件,那么相应的,应该有如下面的一连串子目录:
....\com\runoob\test\Runoob.java
编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上 .class 作为扩展后缀。 例如:
// 文件名: Runoob.java
package com.runoob.test;
public class Runoob {
}
class Google {
}
你可以像下面这样来导入所有 \com\runoob\test\ 中定义的类、接口等:
import com.runoob.test.*;
编译之后的 .class 文件应该和 .java 源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。
<path-one>\sources\com\runoob\test\Runoob.java
<path-two>\classes\com\runoob\test\Google.class
这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java 虚拟机(JVM)可以找到你程序中使用的所有类型。
类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。
<path- two>\classes 是 class path,package 名字是 com.runoob.test,而编译器和 JVM 会在 <path-two>\classes\com\runoob\test 中找 .class 文件。
一个 class path 可能会包含好几个路径,多路径应该用分隔符分开。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。
CLASSPATH 系统变量:
  • 用下面的命令显示当前的CLASSPATH变量
Windows 平台:C:\> set CLASSPATH
UNIX 平台:# echo $CLASSPATH
  • 删除当前CLASSPATH变量内容:
Windows 平台:C:\> set CLASSPATH=
UNIX 平台:# unset CLASSPATH; export CLASSPATH
  • 设置CLASSPATH变量:
Windows 平台: C:\> set CLASSPATH=C:\users\jack\java\classes
UNIX 平台:# CLASSPATH=/home/jack/java/classes; export CLASSPATH
8)作用域
大多数过程型语言都有作用域的概念,作用域由花括号的位置决定,在作用域变量只可用于作用域之前。JAVA语法中,同一个类中不能出现同名的变量,不同于C++可以覆盖其作用域。
JAVA对象不具备和基本类型一样的 生命周期,当用new创建一个JAVA对象时,它可以存活于作用域之外。
{
String s=new String("a string");
}
引用s在作用域终点就消失了,但是s指向的String对象仍然继续占据内存空间,在这段代码中,我们无法再这个作用域之后访问这个对象,因为对它唯一的引用已超出作用域的范围。实事证明,有new创建的对象,只要需要就一直保存下去。JAVA有一个垃圾回收器,用来监视用new创建的所有对象,并辨别那些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。
9)this关键字
this关键字智能在方法内部使用,表示对“调用方法的那个对象”的引用。如果对象方法调用另一个方法没必须用this,只有当需要明确指出对当前对象的引用时,才需要使用this关键字。例如return this。可能为一个类写了多个构造器,有时可能想在一个构造器中调用另一个构造器,以避免重复代码。可以利用this做到,表示“当前对象”。当一个构造器里面用this(参数)调用另一个构造器时,必须将构造器调用置于最起始处,否则编译器会报错。this另一个用法,就是当参数s与实例域s同名时,可用this.s来代表实例域成员就能区分开来。
10)finalize()函数
finalize()不同于c++里面的析构函数,它表示:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。在java中,对象可能不被垃圾回收,并且垃圾回收并不等于“析构”。java并未提供“析构函数”或相似的概念,要做类似的清理工作,必须自己动手创建一个执行清理工作的普通函数。
只有当系统程序濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放,如果程序执行结束,并且垃圾回收器一直都没有释放创建的任何对象的存储空间,则随着程序的退出,那些资源也会全部交给操作系统。java不允许创建局部对象,必须使用new创建对象。
finalize()并不依赖每次都要对它进行调用,这就是对象终结条件的验证。当一个对象可以被清理了,这个对象应该处于某种状态,使它占用的内存可以被安全得释放。比如,当对象打开一个文件后,在清理前本该关闭此文件,如果未关闭此文件对象就被清理了,很明显这存在缺陷。此时,finalize()会检查该对象被清理前的状态,有助于程序员发现错误。一般用protected void finalize(){ }。
11)初始化
成员初始化: 如果是数据成员(字段)是基本类型,每个成员保证都会有一个初始值;如果是对象,引用将获得null;如果是方法里面的局部变量,则不初始化,强行运算报错。
指定初始化: 定义类成员变量的地方赋值,也可以用同样的方法初始化非基本类型的对象。还可以使用调用某个方法来提供初始值,这个方法也可以带有参数,但是这些参数一定是已经被初始化了的。如果未对对象引用指定初始值而强行运算则报错。
构造器初始化: 初始化顺序同上,用一级中按前后顺序,不同级中按级别。如果在调用构造器之前被初始化,后又被初始化一次,则之前的引用的对象将被丢弃,并作为垃圾回收。static关键字不能应用于局部变量,因此它只能作用于域。静态快代码只执行一次,当首次生成这个类的一个对象时,或者首次访问属于那个类的静态数据成员时(即便从未实例化这个类)。
数组初始化: 数组只是相同类型的,用一个标识符名称封装到一起的一个对象序列或基本类型数据序列。编译器不允许指定数组的大小,现在拥有的只是数组的引用,也没对数组对象分配任何空间。为了给数组对象创建相应的存储空间,必须写初始化表达式。对于数组,初始化动作可以出现在代码的任何地方;但也可以使用一种特殊的初始化表达式,它必须在创建数组的地方出现,如:int[] a={1,2,3,4,5};
所有数组都有一个固定成员,就是length。故,数组下标是[0,length-1]之间。如果访问下标过界,则会运行报错。数组元素中的基本类型值会自动初始化成空值,Arrays.toString()方法属于java.util标准类库,它将产生一维数组的可打印版本。如果创建一个非基本类型的数组,那么就创建了一个引用数组。包装器可以直接赋值。
可变参数列表: 利用一个数组来包裹要传递的实参。不可出现同意同名的不同方法
1、“用数组包裹实参”的做法可以分成三步:首先,为这个方法定义一个数组型的参数;然后在调用时,生成一个包含了所有要传递的实参的数组;最后,把这个数组作为一个实参传递过去。
2、在一个形参的“类型”与“参数名”之间加上三个连续的“.”(即“...”,英文里的句中省略号),就可以让它和不确定个实参相匹配。而一个带有这样的形参的方法,就是一个实参个数可变的方法。只有最后一个形参才能被定义成“能和不确定个实参相匹配”的。因此,一个方法里只能有一个这样的形参。另外,如果这个方法还有其它的形参,要把它们放到前面的位置上。编译器会在背地里把这最后一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,表明这是个实参个数可变的方法。
3、只要把要传递的实参逐一写到相应的位置上,就可以调用一个实参个数可变的方法。不需要其它的步骤。sumUp(1, 3, 5, 7); sumUp(new int[]{1, 2, 3, 4}); sumUp();注意这时传递过去的是一个空数组,而不是null。这样就可以采取统一的形式来处理,而不必检测到底属于哪种情况。
4、转发个数可变的实参:有时候,在接受了一组个数可变的实参之后,还要把它们传递给另一个实参个数可变的方法。因为编码时无法知道接受来的这一组实参的数目,所以“把它们逐一写到该出现的位置上去”的做法并不可行。不过,这并不意味着这是个不可完成的任务,因为还有另外一种办法,可以用来调用实参个数可变的方法。在J2SE 1.5的编译器的眼中,实参个数可变的方法是最后带了一个数组形参的方法的特例。因此,事先把整组要传递的实参放到一个数组里,然后把这个数组作为最后一个实参,传递给一个实参个数可变的方法,不会造成任何错误。
public class PrintfSample
{
public static void main(String[] args)
{
printOut("Pi:%f E:%f\n", Math.PI, Math.E);
}

private static void printOut(String format, Object... args)
{
System.out.printf(format, args);
}
}
尽管在背地里,编译器会把能匹配不确定个实参的形参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示“能匹配不确定个实参的形参”和“数组形参”完全没有差异。一个明显的差异是,如果按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会导致一个“cannot be applied to”的编译错误。
private static void testOverloading(int[] i)
{
System.out.println("A");
}

public static void main(String[] args)
{
testOverloading(1, 2, 3);//编译出错
}
由于这一原因,不能在调用只支持用数组包裹实参的方法的时候(例如在不是专门为J2SE 1.5设计第三方类库中遗留的那些),直接采用这种简明的调用方式。如果不能修改原来的类,为要调用的方法增加参数个数可变的版本,而又想采用这种简明的调用方式,那么可以借助“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法来近似的达到目的。
5、当个数可变的实参遇到泛型:新增了“泛型”的机制,可以在一定条件下把一个类型参数化。例如,可以在编写一个类的时候,把一个方法的形参的类型用一个标识符(如T)来代表,至于这个标识符到底表示什么类型,则在生成这个类的实例的时候再行指定。这一机制可以用来提供更充分的代码重用和更严格的编译时类型检查。不过泛型机制却不能和个数可变的形参配合使用。不能拿用标识符来代表的类型来创建这一类型的实例。
private static void testVarargs(T... args) {//编译出错}
六、枚举类型
创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。
package com.hmw.test;
public enum EnumTest {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
这段代码实际上调用了7次 Enum(String name, int ordinal):
new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);
... ...

对enum进行遍历和switch的操作示例代码:
public class Test {
public static void main(String[] args) {
for (EnumTest e : EnumTest.values()) {
System.out.println(e.toString());
}
System.out.println("----------------我是分隔线------------------");
EnumTest test = EnumTest.TUE;
switch (test) {
case MON:
System.out.println("今天是星期一");
break;
case TUE:
System.out.println("今天是星期二");
break;
// ... ...
default:
System.out.println(test);
break;
}
}
}
enum 对象的常用方法介绍:
int compareTo(E o)
比较此枚举与指定对象的顺序。
Class<E> getDeclaringClass()
返回与此枚举常量的枚举类型相对应的 Class 对象。
String name()
返回此枚举常量的名称,在其枚举声明中对其进行声明。
int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
String toString()
返回枚举常量的名称,它包含在声明中。
static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。

七、初始化与清理
创建Java的对象最普遍发的方法是使用new方法,如下所示。而创建对象必须使用构造器,构造器实际就是Java对象初始化的方法,用户可以在该方法中添加自定义初始化行为。
Object obj = new Object(); // 左侧为声明对象,右侧为实际创建一个对象
构造器它是一个隐含为静态的无返回值的方法,名称与类名相同,编译期会自动调用该方法。如果用户没有创建构造器,编译期会为你自动生成一个默认构造器。总之,构造器个数至少有一个。构造器可以有多个,它可以用户自己选择如何初始化对象,这里是使用重载(Overload)的方法。
Java的显著优点就是Java有良好的垃圾清理机制,C++中创建对象,使用对象后,需要使用delete操作符删除对象,就会调用对应的析构函数。而Java中没有析构函数,Java的finalize()并不是类似C++的析构函数,Java的finalize()只是用来回收本地方法(c/c++)占用的内存(调用本地方法类似free)。通常意义上来讲,Java程序员只需创建对象,而不需我们自己去销毁对象,因为垃圾回收机制会帮我们回收对象,虽然不知道什么时候回收,是否会被回收。
  然后可能会出现这种情况,类可能要在生命周期内执行一些必需的清理活动,这就需要程序员自己书写清理方法,在清理方法中必须注意清理顺序,即其顺序与初始化顺序相反,为防止出现异常,可以将清理动作放入finally中。
try {
// 程序编码与异常处理
} finally {
x.dispose();
}
就是执行一个相关函数。

八、 为什么不允许从静态方法中访问非静态变量?
你在Java中不能从静态上下文访问非静态数据只是因为非静态变量是跟具体的对象实例关联的,而静态的却没有和任何实例关联。
有这样一段话,“由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。” 怎么深层次理解这句话呢?
深层次的解释是,“静态方法可以不通过对象进行调用”也就是说这个方法在对象尚未创建的时候就可以调用,而此时对象尚未创建,(非静态)成员变量根本都还不存在,何谈访问?
如果允许调用其他非静态变量,会引起什么后果么?不是允许不允许的问题,是这个时候非静态成员变量都还不存在(他是伴随着对象的创建而创建的),根本无法访问。





1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值