java基础(四)

类和对象

前面记录的都是java面向过程的记录,而现在起,则是面向对象的开始。也正是java的魅力所在。

类是对象的抽象化,对象是类的实例化。怎么说呢,类,定义某个事物的特征,比如球,有半径,有材质,有厚度,有弹性。而对象呢,根据具体事物来说明,半径2cm,材质塑料,厚度1mm,弹性一般,这可能就是乒乓球,而其他球也可以根据类的定义来实例化(对象化)。所以才有万物皆对象,我们可以抽象任何事物的特征,根据不同的特征组合来表述现有的事物。

定义一个类

从我们学习java开始,就已经接触类。

1
2
3
4
5
public class HelloWorld {				//一个类只要唯一一个公共类
public static void main(String[] args) { //main方法
System.out.println("Hello, World"); //输出Hello,World
}
}

怎么用对象呢,都说new一个女朋友,我们把程序改改

1
2
3
4
5
6
7
8
9
10
public class HelloWorld {
public static void main(String[] args) {
HelloWorld hi = new HelloWorld(); //new一个HelloWorld类
hi.printHello(); //使用HelloWorld()类中的非静态方法printHello()
}
void printHello() //声明一个输出Hello,World的方法
{
System.out.println("Hello,World");
}
}

我们分析为这两个的区别。当然首先我们应该也知道,我们写.java的代码,编译器编译为.class的字节码,最后在虚拟机(JVM)运行。这是java之所以流行的原因,跨平台。一次编译,处处运行。

我们的代码都在内存上运行,所以要涉及栈,堆,方法区。栈的特点,连续空间,先进后出,就想乒乓球桶一样,最后放进来的一定最先出来。堆在内存中是随机,无序存放的,不要与数据结构的堆混淆。方法区存放代码块的地方,类,静态的,常量,方法名等等,而方法区是在堆里的。

第一个代码,我们的代码名为HelloWorld,编译器会先找到HelloWorld的类,把该类的相关代码放进方法区,并在栈中开辟一个空间。接着编译器找到main方法,程序运行的入口。一步步运行,只有一个输出,打印,程序运行结束,关闭栈,关闭该程序其它占用的内存(堆)。

第二个代码,前面都一样,从main开始,遇到hi的变量名,放入栈中,hi = null; 然后new一个HelloWordl类的引用,既然new,则又在栈中开辟第二个空间,存放helloWordl类的引用地址,并new的类的引用放到了堆里,里面有printHello方法,使用hi.printHello方法后,关闭第二个栈和该类占用的内存,接着程序结束,关闭第一个栈,关闭该程序其他占用的内存。

构造方法

我们在new HelloWorld()方法时,其实调用了HelloWorld的默认构造方法。构造方法也可以重写,下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HelloWorld {

private String hi;
public HelloWorld(){} //默认构造方法
public HelloWorld(String _hi)
{
hi = _hi;
}

public static void main(String[] args) {
//HelloWorld hello = new HelloWorld();
HelloWorld hello = new HelloWorld("你好,世界");
HelloWorld hello2 = new HelloWorld();
hello.printHello();
hello2.printHello();
}
void printHello()
{
System.out.println("Hello,World!"+hi);
}
}

构造方法的注意:
1、构造方法没有返回类型、返回值。
2、如果没有重写(重载:除了方法名和内容其它都可以改变;重写:除了方法名和返回类型其它都可以改变),只会调用默认构造方法。
3、重写构造方法,默认构造方法也就没有了,编译器觉得你都自己写了,那我给你的,你也没必要用了。所以还想要默认构造方法,自己写一个。

其它注意:
在以前写代码,没有赋值的变量如果被使用,编译器会报错,但在这hello2中,我new的默认构造方法,hi变量没赋值,所以输出String的默认值”null”。简单说明下,在方法内的变量称为局部变量,只归该方法使用,默认没有初始化。而在方法外,类内的的变量称为成员变量(全局变量),能在该类任意一个位置使用,自带初始化,String引用类型,默认值null。

关键字this

this指向当前类,可以用当前类的变量或构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package io.github.java04;

public class graphArea {

private int a; //长
private int b; //宽
private int c; //高
private int area; //面积

//默认构造方法
graphArea(){}

//正方形的面积
public graphArea(int a)
{
this.a = a; //this.a,当前类的变量, a该方法的变量
}

//矩形的面积
public graphArea(int a, int b)
{
this.a = a;
this.b = b;
}

//长方体的面积
public graphArea(int a, int b, int c)
{
this(a,b); //this()调用相应的构造方法
this.c = c;
}

//获取面积
public int getArea()
{
if(b==0&&c==0) area = a*a; //方法没有局部变量,调用的成员变量
else if(c==0) area = a*b;
else area = (a*b+a*c+b*c)*2;
return area;
}

public static void main(String[] args) {

//正方形
graphArea cube = new graphArea(3);
System.out.println("正方形的面积:" + cube.getArea());

//矩形
graphArea rect = new graphArea(2, 3);
System.out.println("矩形的面积:" + rect.getArea());

//长方体
graphArea cuboid = new graphArea(2, 3, 4);
System.out.println("长方体的面积:" + cuboid.getArea());
}
}

注意:this. ,注意’.’,指代调用当前类的变量。this()调用本类的其他构造方法时,只能放在构造方法的第一行。与this()对应的还有super()指代父类,下面会有记录。

关键字static和final

静态的,第三章也简单带过,这里在带过一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class HelloWorld {

public static int a = 5;
public final static int b = 10;
int c = 15;
public static void main(String[] args) {

HelloWorld h1 = new HelloWorld();
HelloWorld h2 = new HelloWorld();
System.out.println(h1.a); //输出h1.a的值

a = 16;
System.out.println(h1.a); //a的值改变了,输出h1.a的值

h1.a = 10; //h1.a的值改变了,输出h2.a的值
System.out.println(h2.a);

//b = 15; 常量不可修改
//System.out.println(c); //编译器报错

printHello();

}
public static void printHello()
{
System.out.println("Hello,World!");
}
}

static:静态的,一开始加载到方法区,可以在方法中直接使用,如第21行直接调用printHello()方法,没有用类去调用。
第12行把a变为16,对应的第19行输出非静态变量c编译器直接报错。
既然static是在方法区,如果静态的值改变,那么其它引用的类的a值也改变,参考代码。

final:会用就行,常量,一开始也加载到方法区,当final修饰,所以不可修改,修饰方法也就没有继承,比如Math类,String类等等。

方法区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HelloWorld {
static {
System.out.println("你好,static方法块");
}
{
System.out.println("你好,普通方法块");
}

public void sayHi()
{
System.out.println("sayHi方法");
}

public static void main(String[] args) {

System.out.println("main方法执行");
HelloWorld p = new HelloWorld();

System.out.println("------------");

p.sayHi();

System.out.println("------------");
new HelloWorld().sayHi();
}

}
1
2
3
4
5
6
7
8
你好,static方法块
main方法执行
你好,普通方法块
------------
sayHi方法
------------
你好,普通方法块
sayHi方法

我们慢慢分析,静态、常量、方法名、类一开始加载到方法区,所以最新输出static方法块,之后开始执行main方法,p引用HelloWorld对象,开辟新的栈,堆加载一个HelloWorld对象,由于static已经加载,所以不在执行static方法块,输出普通的方法块,接着画出横线,使用sayHi()方法.又画出横线,又新建一个对象,开辟新的栈,加载新对象,使用普通方法块,程序结束,关闭所有占用的内存。

面向对象

以上是类的简单示例,和对象的基本认识。现在开始就是面向对象的三大特性:封装,继承,多态。

封装

在求面积那,就简单封装了一下,用构造方法设置边长,面积只能getArea()获取,我们可以在这些设置或获取的方法中做相应的限制。比如输出一个年龄,如果没有封装,可以随意输入,-1,-10,1000000,可现实中,生物中生命不可能为负数,也不可能活的非常非常的长,可以设置if年龄在0到150之间为正确输入,如果超过,提示用户错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class years {



public static void main(String[] args) {
System.out.println("输入年龄,范围在0<year<=150之间:");
_year y = new _year();
y.setYear(151);
y.ye = 151;
System.out.println("year:" + y.getYear());
System.out.println("ye:" + y.ye);
}
}

class _year
{
private int year;
int ye;
public int getYear() {

if(year == 0)
{
System.out.println("年龄错误");
return 0;
}
return year;
}

public void setYear(int year) {
if(year>0 && year <=150) this.year = year;
}

}

封装基本就这样,就像我们现在看电视手机,用户不需要知道内部是怎么样的,怎么运行的,我们也不可以把内部代码放出来给用户修改。只需要给用户一个遥控器,一个触摸屏,保持一个合理的使用方法即可。也像我们的身体,内部怎样工作不清楚,我们只要观四方听八面走万里就行。

继承

子类继承父类,为啥,简单一句,提高开发效率,避免代码冗余。但在java中,继承只能单继承,并且所有类都默认继承Object,只有接口才能多继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.age = 10;
//a.weight 不可用,private只能在自己的类使用
//a.height 父类不可以用子类的任何属性
a.jiao(); //打印 动物叫了

dog d = new dog();
d.age = 15;
d.height = 100;
//d.weight 子类不可以用父类的私有属性
d.jiao(); //打印 汪汪汪
}
}

class Animal extends Object //默认继承Object类,可以不写
{
int age;
private int weight = 100;

public void jiao()
{
System.out.println("动物叫了");
}
}

class dog extends Animal
{
int height;
public void jiao()
{
System.out.println("汪汪汪");
}
}

父类Animal,子类dog,父类的自己的任何属性可以自己调用,但父类不可以调用子类的属性,就像人一样,我们的身体是从父母得来的,而父母的身体自然也不可能是从子女来。子类可以用父类除了私有的任何属性,也可以用自己的属性,也可以定义和父类一样的变量,也可以重写父类的方法。这就是继承。

关键字super

super()调用的是父类的构造方法,在子类中使用,也是放到构造方法的第一行。所以,this()和super()只能调用任意一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class superTest {
public static void main(String[] args) {
cat c = new cat();

System.out.println("---------------");

c.eat();
}
}
class animal
{
animal()
{
System.out.println("我是动物");
}
public void eat()
{
System.out.println("动物在吃东西");
}
}

class cat extends animal
{
cat()
{
//super(); //默认自动调用,并在方法的第一行
System.out.println("我是猫");
}
public void eat()
{
super.eat();
System.out.println("猫在吃猫粮");
}
}

上面只是new cat类,程序运行时,默认调用cat的默认构造方法,在cat的构造方法里默认调用了super(),也就是父类的默认构造方法,无论有没有super(),都调用父类的默认构造方法,而父类又向上调用super(),直到Object类的默认构造方法,最后一层层返回,返回到animal的默认构造方法的内容,运行结束又返回到cat的默认方法的内容,最后结束运行。整体就是递归调用的思想。

而横线下面,调用了eat(),在子类中,super.eat()调用了父类的方法。

当然如果父类没有默认的构造方法,但又想用父类的构造方法,就可以用super(参数名)的形式。

多态

同种物,不同的形态,就像陆地哺乳动物,眼鼻耳口肢体,外形都可以类似,但各自习惯,能力,体型等不同而产生的各种形态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AnimalTest {
public static void main(String[] args) {
Animal a = new dog(); //向上转型,把dog类变为Animal类
a.jiao(); //打印 汪汪汪
//重写了父类的jiao()方法,所以打印了子类的jiao()方法
//a.height 错误,虽然new的是dog(),但父类没有,
System.out.println(a.age); //打印的也只是父类的15,并不是子类的10

dog d = (dog)a; //向下转型,a是Animal类,转化为dog类
System.out.println(d.age); //转型后,age的值才是子类的10
}
}

class Animal
{
int age = 15;
private int weight = 100;

public void jiao()
{
System.out.println("动物叫了");
}
}

class dog extends Animal
{
int height;
public void jiao()
{
System.out.println("汪汪汪");
}
}

把代码改改,用父类的引用指向了子类,简单意义就是多态,有父类的共同特点,也只可以用父类的属性。但是,子类自己定义的属性不能使用,子类与父类一样的变量名,也只使用父类的。这就是动态绑定,如果子类重写父类的方法,则用子类的,没有向父类找,直到找到最后一次重写的方法,如果找到了Object类也没有找到,则编译器自动报错。

toString()

toString()是Object类的方法,下面是它默认的方法,我的jdk是1.8,所以可能与其它版本有差异,但大致都差不多。

1
2
3
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

可以看的出来,返回了类的名称+@+类的哈希地址下面是示例。

1
2
3
4
5
6
7
8
9
10
11
12
package io.github.java04;

public class toStringTest {
public static void main(String[] args) {
car c = new car();
System.out.println(c.toString());
}
}

class car
{
}
1
2
//这是得到的答案
io.github.java04.car@7852e922

我们一般会重写toString()方法,返回我们想得到的信息,比如类名,结果,而不是一串看不懂的哈希值。

equals()

equals()在后面String学习,我们都是通过equals()来判断String的内容是否相等,而不用 == ,因为在java中, == 是判断两个对象是否一样,或基本数据类型才比较值。而不是对象的内容。但是在String中是重写了该方法。下面是Object类的equals()。

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

看的出来,返回的是对象是否相等,并没有比较值,所以我们在用equals(),想比较值是否相等,一定要重写该方法。下面是错误示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class equalsTest {
public static void main(String[] args) {
circular c1 = new circular(1);
circular c2 = new circular(1);

System.out.println(c1.equals(c2)); //打印 false

}
}

class circular
{
private int radius;

public circular(int radius)
{
this.radius = radius;
}

public int getRadius()
{
return radius;
}
}

下面是重写后的equals()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class equalsTest {
public static void main(String[] args) {
circular c1 = new circular(1);
circular c2 = new circular(1);

System.out.println(c1.equals(c2)); //打印 true

}
}

class circular
{
private int radius;

public circular(int radius)
{
this.radius = radius;
}

public int getRadius()
{
return radius;
}

@Override
public boolean equals(Object obj) {

if(obj instanceof circular) //instanceof,如果左边的对象是输入右边的类,则返回true
{
return radius == ((circular)obj).radius;
}
return this == obj;
}
}

好久没更了,但一直在想办法进步,努力,提升自己,大家一起加油吧!!!

谢谢您对我的支持
0%