Java 类与对象
面向对象程序设计技术
面向对象程序设计技术(OOP)是一种计算机编程架构;
对象是现实世界客观实体,有特定的状态和行为。
类是一个模板,它描述一类对象的行为和状态;依照现实世界的实体特点,把复杂的事物所共有的状态和行为抽象封装.
该技术的主要目标是提高代码的可重用性、可扩充性和程序自动生成,从而提高编程效率.
面向对象的基本特征
① 封装性:封装是将东西包围起来通过自己想定义的方式获取。面向对象程序语言把过程和数据包围起来,通过定义的接口访问数据。
② 抽象性:抽象是对数据进行实例分析,抽取其共同性质的结果,抽象包括两个方面,一是过程抽象,二是数据抽象。
③ 继承性:继承是是基于层次关系的不同类共享数据和操作的一种机制。父类定义了其所有子类的公共属性和操作,在子类中除了定义自己特有的属性和操作外,还可以对父类(或祖先类)中的操作重新定义其实现方法,即重载。
④ 多态性:多态性是是指同一操作作用于不同对象上可以有不同的解释,并产生不同的执行结果。动态绑定,是指在程序运行时才将消息所请求的操作与实现该操作的方法进行连接
注:Java的面向对象机制从C++继承发展而来
完善了C++类的封装、继承、多态和抽象等基本概念;
放弃了C++的多重继承、友元类和运算符重载等易产生歧义且安全性差的诸多特性;
采用单重继承+接口的方式实现多重继承功能,提供内存自动管理和异常处理机制
类的构成
类是数据以及对数据的一组操作的封装体。
成员变量——类中的数据,反映类的状态和特征;
成员方法——类中数据的操作,表示类的行为能力;
构造方法—为创建类的实例所使用。 类的成员—类中的成员变量和成员方法的统称。
注意:与数据类型相似,类是只定义数据及对数据操作的模板, 类本身并不真正参与程序运行,实际参与程序运行的是类的对象。
类声明的格式
[修饰符] class 类<泛型> [extends 父类][implements 接口列表]
{
成员变量的声明;
成员方法的声明及实现;
类的构造方法;
}
必须项——关键字class和自定义的类名;
可选项——方括号[]中的;
<类>、<泛型>、<父类>、<接口>——合法的Java标识符
类名标识符——Java预定通常首字母大写。
<修饰符>——说明类属性,如public访问权限、abstract 抽象类或final最终类。
<泛型>——类的参数,带参数的类称为泛型类。
成员方法——用来描述对成员变量的操作,不但要声明方法,还要实现方法。
成员变量声明格式
[修饰符] 数据类型 变量[=表达式] {,变量[=表达式]};
<修饰符>——说明成员属性的关键字,包括访问权限public、 protected、private以及静态成员static、最终方法和最终变量final等。
成员方法声明格式
[修饰符] 返回值类型 方法([参数列表]) [throws 异常类]
{
语句序列;
[return [返回值]];
}
<修饰符>——说明成员属性的关键字,包括访问权限public、 protected、private以及静态成员static、最终方法和最终变量final等。
Java的成员修饰符
private:访问权限仅限于类的内部,是一种封装的体现,例如,大多数的成员变量都是修饰符为private的,它们不希望被其他任何外部的类访问;
protected:子类可以访问父类使用它修饰的成员,是父类传递给子类的继承信息。
public:表示成员是公开的,所有其他类都可以访问;它具有最大的访问权限,是对象或类对外的一种接口的形式。
无修饰词(默认):表示包访问权限(相当于c++的友元) ,同一个包内可以访问,访问权限是包级访问权限;比 protected大,比public小。但是有些公司规范规定不能使用默认修饰符。
this引用
Java类中的每个成员方法都可以使用代词this引用调用该方法的当前对象自己。
(1) 指代对象本身
this用于指代调用成员方法的当前对象自身。
(2) 访问本类的成员变量和成员方法通过this引用当前对象的成员变量,调用当前对象的成员方法。
this . 成员变量
this . 成员方法([参数列表])
一个成员方法中,若没有与成员变量同名的局部变量或形式参数,则this引用可以省略。
当局部变量或形式参数与成员变量同名时,方法体中默认的是局部变量或形式参数,而要访问成员变量必须使用this。
instance of 对象运算符
对象运算符instanceof 判断一个对象是否属于指定类及其子类,返回boolean类型。
Time1 t=new Time1( );
t instanceof Time1; //返回true,t是Time1类的实例
类的成员方法的参数
形参为普通简单量时,采用传值的方式;
形参为对象变量时,采用是传引用的方式
1. Java参数传值
适用范围:8种基本数据类型:boolean , byte , char ,short , int ,long ,float ,double;String对象
特点:在内存中复制一份数据,把复制后的数据传递到方法内部
作用:在方法内部改变参数的值,外部数据不会跟着发生改变
public class test {
public void f1(int n){
n = 10;
}
public void f2(String s){
s = "abcd";
}
public static void main(String[] args){
test obj = new test();
int m = 5;
obj.f1(m);
System.out.println(m);
String s1 = "test string";
obj.f2(s1);
System.out.println(s1);
}
}
输出:5,test string
2.Java传参数引用
适用范围:数组、除String以外的其他所有类型的对象,StringBuffer动态字符串是传引用
特点:将对象的地址传递到方法内部
作用:在方法内部修改对象的内容,外部数据也会跟着发生改变
public class test {
public void f3(int[] array){
array[0] = 100;
}
public void f4(StringBuffer buffer) {
buffer.append("StringBuffer");
}
public static void main(String[] args) {
test obj = new test();
int[] arr = {1,2,3,4};
obj.f3(arr);
System.out.println(arr[0]);
StringBuffer str = new StringBuffer("test ");
obj.f4(str);
System.out.println(str);
}
}
输出: 100 test StringBuffer
3.关于缺省值
Java的成员函数的参数不能定义缺省值,可用重载的方式解决这个问题。
类的构造方法
Java变量必须先初始化后使用。
问:Java类定义的对象如何初始化?
答:Java提供了构造方法初始化对象的数据成员
1.基本概念
用于创建类的实例,并对实例的成员变量进行初始化。
构造方法与类同名;构造方法通过new运算符调用。
一个类可以声明多个构造方法对成员变量进行不同需求的初始化;如果类没有定义构造方法,则编译系统会自动插入一个无参数的默认构造器,该构造器不执行任何代码。
构造方法不需要写返回值类型,也不能定义为void,在方法名前面不声明方法类型。
构造方法可以重载,以参数的个数,类型,顺序。
2.缺省构造方法
Java类中如果没有显示定义构造方法,编译器会隐式提供一个没有参数的缺省构造方法;
如果在类中显示定义了构造方法,则不会提供缺省构造方法。
3.重载构造方法
Java支持构造方法重载,重载的构造方法提供创建实例时的多种初始化方案,如指定若干参数的构造方法、默认构造方法、拷贝构造方法等。
由于Java不支持会产生歧义的参数默认值,这些构造方法必须重载,参数列表必须不同。
public class Person {
int id;
int age;
public Person() { /* 构造方法 ,java不支持默认参数*/
id=0;
age=20;
}
public Person(int i) { /* 构造方法重载一 @param i */
id=i;
age=20;
}
public Person(int i,int j) { /*构造方法重载二@param i @param j */
id=i;
age=j;
}
}
4.拷贝构造方法
拷贝构造函数是特殊的构造函数,其参数是该类对象,称为拷贝构造方法,它将创建的新对象初始化为形式参数的实例值,实现对象复制功能。
Java不提供默认拷贝构造方法。
public class Person {
int id;
int age;
public Person(int i,int j) { /* 构造方法 */
id = i;
age = j;
}
public Person(Person p) { /*拷贝构造方法 */
id=p.id;
age=p.age;
}
public static void main(String[] args) {
Person p1 = new Person(30,50);
Person P2 = new Person(p1); //调用拷贝构造方法复制对象p1
}
}
成员方法的重载
一个类中成员变量不能同名,但成员方法可以同名。
重载:一个类中可以有多个同名的成员方法,前提是参数列表必须不同,称为类的成员方法重载。
作用:重载多个方法为一种功能提供多种实现;
区分:第一是参数的个数不一样,第二是参数的类型不一样。
只要这两方面有其中一方面不一样就可以重载构成方法。
注意:如果两个方法仅返回值不同, 而其他都相同。 这个时候并不构成方法的重载, 在编译的时候会报错。
final关键字
1.修饰普通变量
如果Final修饰基本数据类型的变量(包括String),则其数值一旦在初始化之后便不能更改;
public class test{
public static void main(String[] args){
final int i = 10;
final String s = "This is old string";
++i ; //此处是错误,final修饰的变量i不能被修改
s= " This is new string "; //此处错误
}
}
```java
public class test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((b == d));//(1)
System.out.println((a == c));//(2)
System.out.println((a == e));//(3)
System.out.println((a.equals(e)));//(4)
}
}
输出:true true false true
代码解释:
① Java中的==号比较两个对象是否为同一对象;
② 语句(1)说明Java的相同字符串常量在内存分配时,是共享内存,仅创建唯一对象;
③ 语句(2)说明final修饰常量b,在编译时替换了c=b+2中的b,因此,c视为常量值hello2 ,和a共享同一对象;
④ 语句(3)说明e=d+2中的d没有final修饰,e编译时不替换d,e在内存中和a 是不同的两个对象;
⑤ a.equals(e)是比较a和e两个字符串对 象的值。
2.修饰成员变量
当用final作用于类的成员变量时,成员变量必须在定义时或者构造方法中进行初始化赋值;
当final成员变量被初始化赋值后,就不能再被赋值
如果类的所有成员变量都定义为final,类创建的对象可以被视为常对象。
class Mydata{
final private int x;
final private int y;
public Mydata(int i,int j){
x=i; //在此初始化x
y=j; //在此初始化y
}
public void setx(int i){
x=i; //错误,x不能再次修改
}
public void sety(int j){
y=j; //错误,y不能再次修改
}
public void display(){
System.out.println("x="+x);
System.out.println("y="+y);
}
}
当用final修饰的成员变量在定义时初始化,则就不能在构造函数中初始化
如果final修饰的成员变量在构造方法中初始化时,就不能在定义时初始化;
当final成员变量没有在定义时初始化时,就必须显式提供构造函数初始化。
class Mydata{
final private int x=0;
final private int y=0;
public Mydata(int i,int j){
x=i; //定义和构造函数不能同时初始化x
y=j; //定义和构造函数不能同时初始化y
}
public void display(){
System.out.println("x="+x);
System.out.println("y="+y);
}
}
3.修饰引用变量
如果final是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
但引用所指向的对象数据成员**(属性)是可以改变**的。
class Mydata{
private int x;
private int y;
public Mydata(int i,int j){
x=i;
y=j;
}
public void resetXY(int i, int j){
x=i;
y=j
}
public void display(){
System.out.println("x="+x);
System.out.println("y="+y);
}
}
public class test{ public static void main(String[] args){
final Mydata d= new Mydata(1,2);
Mydata d1= new Mydata(10,20);
d=d1; // final修饰的引用不能指向其他对象
d.resetXY(5,10);
d.display();
}
}
4.修饰函数形参
如果final是形参为基本类型变量,则形参作为局部变量传值,对外部变量的值不产生影响,作为局部变量的形参由于有final限定,因此在成员方法中不能被修改;
如果final是形参为引用类型的对象,该形参就不能再次引用其他对象,但传给该形参的实参所引用的对象的数据成员(属性)是可以改变。
public class test{
int x;
public test(int x){
this.x=x;
}
public void f1(final int n){
//n = 10; 错误,final变量不能修改
}
public void f2(final String s){
// s = "abcd";错误,final变量不能修改
}
public void f3(final int[] array){
array[0] = 100;
}
public void f4(final test obj1) {
obj1.x=100;
test obj2= new test(1000); //obj1=obj2;错误,final引用不能变更引用
}
public static void main(String[] args){
test obj = new test(5);
int m = 10;
obj.f1(m);
System.out.println(m);
String s1 = "test string";
obj.f2(s1);
System.out.println(s1);
int[] arr = {1,2,3,4};
obj.f3(arr);
System.out.println(arr[0]);
obj.f4(obj);
System.out.println("obj.x="+obj.x);
}
}
5.修饰成员方法
如果final修饰成员方法,该方法禁止该方法在子类中被覆盖或者重写。
6.修饰Java类
如果final修饰类,表明这个类不能被继承。
遗留一个小问题:final是怎么实现这些功能的?
static变量
static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是 Java语言中没有全局变量的概念。
被static修饰的成员变量和成员方法独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享。
只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内找到它。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的成员方法中使用,但是不能在其他类中通过类名来直接引用。
class StaticDemo {
static int a = 42;
static int b = 99;
static void callme() {
System.out.println("callme a = " + a);
System.out.println("callme b = " + b);
}
void resetAB(int i, int j){
a=i;
b=j;
}
public static void main(String args[]) {
StaticDemo obj = new StaticDemo();
StaticDemo obj1 = new StaticDemo();
obj.resetAB(100,200);
StaticDemo.callme();
System.out.println("StaticDemo.a="+ StaticDemo.a);
System.out.println("StaticDemo.b="+ StaticDemo.b);
System.out.println("obj1.a = " + obj1.a);
System.out.println("obj1.b = " + obj1.b);
}
}
运行结果:
callme a = 100
callme b = 200
StaticDemo.a = 100
StaticDemo.b = 200
obj1.a = 100
obj1.b = 200
注:static成员方法只能访问静态成员变量
class StaticDemo {
static int a = 42;
static int b = 99;
int c = 10;
StaticDemo(int i, int j, int k){
a = i;
b = j;
c = k;
}
static void staticDisplay() {
System.out.println("a = " + a);
System.out.println("b = " + b);
// System.out.println("c = " + c);
// 不能访问非静态数据成员
}
void display() {
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
public static void main(String args[]) {
StaticDemo obj = new StaticDemo(10,20,30);
StaticDemo obj1 = new StaticDemo(40,50,60);
System.out.println("StaticDemo call staticDisplay():");
StaticDemo.staticDisplay();
System.out.println("obj call display():");
obj.display();
System.out.println("obj1 call display():");
obj1.display();
}
}
输出:
StaticDemo call staticDisplay(): a = 40 b = 50
obj call display(): a = 40 b = 50 c = 30
obj1 call display(): a = 40 b = 50 c = 60
构造代码块
构造代码块的作用是给对象进行初始化。
对象建立时就运行构造代码块,而且优先于构造函数执行。只有对象建立时,才会运行构造代码块,类不能调用构造代码块的,而且构造代码块与构造函数的执行顺序是前者先于后者执行。
构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
public class Person {
{
//定义第一个初始化块
int a = 6;//初始化块中
if (a >4){
System.out.println("Person初始化块:局部变量a的值大于4");
}
System.out.println("Person 的第一个初始化块");
}
//定义无参数的构造器
public Person(){
System.out.println("Person类的无参数构造器");
}
public static void main(String[] args){
new Person();
}
//定义第二个初始化块
{
System.out.println("Person 的第二个初始化块");
}
}
程序运行结果:
Person初始化块:局部变量a的值大于4
Person 的第一个初始化块
Person 的第二个初始化块
Person类的无参数构造器
静态代码块
它是随着类的加载而执行,只执行一次,并优先于主函数。具体说,静态代码块是由类调用的。类调用时,先执行静态代码块,然后才执行主函数的。
静态代码块其实就是给类初始化的,而构造代码块是给对象初始化的。
静态代码块中的变量是局部变量,与普通函数中的局部变量没有区别。
一个类中可以有多个静态代码块
public class Test{
staitc int cnt=6; (这个static可以去掉吗?why?)
static{
cnt+=9;
}
public static void main(String[] args) {
System.out.println(cnt);
}
static{
cnt/=3;
}
}
程序运行结果: 5
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
public static void main(String[] args) {
HelloA a=new HelloA();
}
}
程序运行结果:
A的静态代码块
A的构造代码块
A的构造函数
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
public static void main(String[] args) {
HelloA a=new HelloA();
HelloA b=new HelloA();
}
}
程序运行结果:
A的静态代码块
A的构造代码块
A的构造函数
A的构造代码块
A的构造函数
public class InitialOrderTest {
public static String staticField = "静态变量";
public String field = "变量";
static {/* 静态初始化块 */
System.out.println( staticField );
System.out.println( "静态初始化块" );
}
{ /* 初始化块 */
System.out.println( field );
System.out.println( "初始化块" );
}
public InitialOrderTest() { /* 构造器 */
System.out.println( "构造器" );
}
public static void main( String[] args ) {
new InitialOrderTest();
}
}
程序运行结果:
静态变量
静态初始化块
变量
初始化块
构造器
另:继承中的静态代码块和初始化代码块
public class HelloA {
public HelloA(){//构造函数
System.out.println("A的构造函数");
}
{//构造代码块
System.out.println("A的构造代码块");
}
static {//静态代码块
System.out.println("A的静态代码块");
}
}
public class HelloB extends HelloA{
public HelloB(){//构造函数
System.out.println("B的构造函数");
}
{//构造代码块
System.out.println("B的构造代码块");
}
static {//静态代码块
System.out.println("B的静态代码块");
}
public static void main(String[] args) {
HelloB b=new HelloB();
}
}
程序运行结果:
A的静态代码块
B的静态代码块
A的构造代码块
A的构造函数
B的构造代码块
B的构造函数
class Parent {
public static String p_StaticField = "父类--静态变量"; /* 变量 */
public String p_Field = "父类--变量";
protected int i = 9;
protected int j = 0;
/* 静态初始化块 */
static {
System.out.println( p_StaticField );
System.out.println( "父类--静态初始化块" );
}
{/* 初始化块 */
System.out.println( p_Field );
System.out.println( "父类--初始化块" );
}
public Parent() /* 构造器 */ {
System.out.println( "父类--构造器" );
System.out.println( "i=" + i + ", j=" + j );
j = 20;
}
}
public class SubClass extends Parent {
public static String s_StaticField = "子类--静态变量";
public String s_Field = "子类--变量";
static {/* 静态初始化块 */
System.out.println( s_StaticField );
System.out.println( "子类--静态初始化块" );
}
{ /* 初始化块 */
System.out.println( s_Field );
System.out.println( "子类--初始化块" );
}
public SubClass() /* 构造器 */ {
System.out.println( "子类--构造器" );
System.out.println( "i=" + i + ",j=" + j );
}
public static void main( String[] args ) {/* 程序入口 */
System.out.println( "子类main方法" );
new SubClass();
}
}
程序运行结果:
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
子类main方法
父类--变量
父类--初始化块
父类--构造器
i=9, j=0
子类--变量
子类--初始化块
子类--构造器
i=9,j=20
程序分析:
① 执行父类的静态代码块,并初始化父类静态成员变量
② 执行子类的静态代码块,并初始化子类静态成员变量
③ 执行父类的构造代码块,执行父类的构造函数,并初始化父类普通成员变量
④ 执行子类的构造代码块, 执行子类的构造函数,并初始化子类普通成员变量