【Java(二)】——对象与类
对象与类
基础
1、面向对象OOP,由类构造对象的过程称为创建类的实例。所有类都源自于一个超类Object
2、对象的特性:行为(方法)、状态、标识
3、依赖:如果一个类的方法操纵另一个类的对象,我们就说一个类的方法依赖于另一个类的对象
4、聚合:类A的对象包含着类B的对象
5、继承:特殊与一般的关系。具有特殊性的子类包含了一些用于优先处理的特殊方法,其他的方法都是从具有一般性的父类继承而来的。一般而言,如果类A继承类B,类A不但包含从类B继承的方法,还会拥有一些额外的功能。
6、使用对象:构造对象,指定初始状态,应用对象中方法。
7、对象≠对象变量,对象是一个实例化的类,对象变量可以引用某一个类的对象。一个对象变量斌没有实际包含一个对象,而仅仅引用一个对象。在Java中任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用
不可以将一个方法应用于null对象,因为null对象实际上是说这个变量没有引用任何对象,它根本就不是一个对象
8、Java的变量对象相当于C++的对象指针Date birth;
等同于Date* birth=new Date();
9、Java所有的对象都存储在堆中。当一个对象包含另一个对象变量时,这个变量依然包含指向另一个堆对象的指针。
10、在Java中,如果使用一个没有初始化的指针,运行系统将会产生一个运行时错误,而不是生成一个随机的结果。同时,不必担心内存管理问题,垃圾收集器将会处理相关的事宜。在Java中,必须使用clone
方法获得对象的完整拷贝。
11、Java中所说的构造器就是C++里面说的类的构造方法;实例域就是C++所说的成员变量
12、一个日历程序的例子:
public class CalendarTest {
public static void main(String[] args) {
//不适用构造器来构造LocalDate对象,而是使用静态工厂方法代表调用的构造器
LocalDate date=LocalDate.now();
int month=date.getMonthValue();
int today=date.getDayOfMonth();
//将date设置为这个月的第一天,并得到这一天为星期几
//minusDays():设置当前日期之后或之前n天的日期,相当于当前日期减去today-1天
date=date.minusDays(today-1); //设置本月的第一天,不是很懂
DayOfWeek weekday=date.getDayOfWeek();
int value=weekday.getValue();
System.out.println("Mon\tTue\tWed\tThu\tFri\tSat\tSun");
for(int i=1;i<value;i++)
System.out.print(" \t");
while(date.getMonthValue()==month)//如果还是这个月的
{
System.out.printf("%3d",date.getDayOfMonth());
if(date.getDayOfMonth()==today)
System.out.print("*\t");
else
System.out.print(" \t");
date=date.plusDays(1);
if(date.getDayOfWeek().getValue()==1)
System.out.println();
}
//为输出下一个月做准备,如果下一个月的第一天是周一,那在循环的时候就已经还换行了
//如果不是周一,为了下个月输出的时候是新的一行,所以就在这里换行了
if(date.getDayOfWeek().getValue()!=1)
System.out.println();
}
}
13、构造器需要注意的:
①构造器与类同名
②每个类可以有一个以上的构造器
③构造器可以有0、1或多个参数
④构造器没有返回值
⑤构造器总是伴随着new操作一起调用
14、一个类最基本的内容:私有数据域+公有的域访问器方法+公有的域更改器方法
15、如果需要返回一个可变对象的引用,应该首先对它进行克隆。对象clone是指存放在另一个位置上的对象副本。
16、final实例域:将实例域定义为final,构建对象时必须初始化这样的域。必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。对于final修饰的可变的类,只是说这个变量指示的内存空间不会变,但是内存空间里面的东西还是可以变的,也就是说指向的对象的域变量的值是可以改变的
静态域与静态方法
17、静态域:用static修饰的域,每个类只有一个这样的域,静态域属于类,不属于对象,n个对象,但是只能共享唯一一个静态域(也被叫做类域)
18、静态常量:public static final double PI=3.1415926358979;
因为是final,所以尽管域是public也不怕域被修改
19、静态方法:静态方法是一种不能像对象实施操作的方法。例如,Math类的pow方法就是一个静态方法。表达式Math.pow(x,a);
在运算时,不使用任何Math对象,也就是说没有隐式的参数。可以认为静态方法是没有this参数的方法。静态方法不能操作对象,但是可以访问自身类中的静态域。最好使用类名来调用静态方法
20、使用静态方法的两种情况:
①一个方法不需要访问对象状态,其所需的参数都是通过显示参数提供
②一个方法只需要访问类的静态域(访问static域)
21、Java中的static的意义与c++一样,与c不一样
22、工厂方法:静态方法,直接使用类来调用静态方法
28、main()方法:一个静态方法,不对任何对象进行操作,在启动程序时没有任何一个对象,静态的main方法将执行并创建程序所需要的对象。每一个类可以有一个main方法,主要是用于对类进行单元测试,看看这个类里面有没有什么毛病
23、
public class StaticTest {
public static void main(String[] args) {
//定义一个数组并初始化
Employee[] staff=new Employee[3];
staff[0]=new Employee("loan", 10000);
staff[1]=new Employee("lyna", 8000);
staff[2]=new Employee("易", 15000);
for(Employee em:staff){
em.setId();
System.out.println("name: "+em.getName()+",id: "+em.getId()+",salary: "+em.getSalary());
}
//使用静态方法
int n=Employee.getNextId();
System.out.println("下一个变量的ID: "+n);
}
}
//内部类
class Employee
{
//静态域,属于类,而不属于对象
private static int nextId=1;
private String name;
private double salary;
private int id;
//构造器
public Employee(String name,double salary) {
this.name=name;
this.salary=salary;
this.id=0;
}
public static int getNextId() {
return nextId;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public int getId() {
return id;
}
public void setId() {
id=nextId;
//只能被初始化一次,也就是说所有变量共享一个nextId域,并不是说它不能被改变
nextId++;
}
}
方法参数
24、参数传递方法:
①按值调用:方法接收的是调用者提供的值。方法得到的是所有参数值的一个拷贝,方法不能修改传递给它的任何参数变量的内容
②按引用调用:方法接收的是调用者提供的变量地址。
③一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
25、Java中对对象采用的不是引用调用,实际上对象引用是按值传递。Java中方法参数的使用情况:【不要搞混】
①一个方法不能修改一个基本数据类型的参数(数值型或布尔型)
②一个方法可以改变一个对象参数的状态
③一个方法不能让对象参数引用一个新的对象
26、
public class ParamTest {
public static void main(String[] args) {
System.out.println("Testing tripleValue:");
double percent=10;
System.out.println("Before: percent="+percent);
tripleValue(percent);
System.out.println("After: percent="+percent);
System.out.println("\nTesting tripleSalary:");
Employee2 loan=new Employee2("loan", 10000);
System.out.println("Before: salary="+loan.getSalary());
tripleSalary(loan);//传入的是对象
System.out.println("After: salary="+loan.getSalary());
System.out.println("\nTesting swap");
Employee2 a=new Employee2("Lyna", 8000);
Employee2 b=new Employee2("zyn", 9000);
System.out.println("Before: a="+a.getName());
System.out.println("Before: b="+b.getName());
swap(a,b);
System.out.println("After: a="+a.getName());
System.out.println("After: a="+b.getName());
}
public static void tripleValue(double x)
{
x=x*3;
System.out.println("End of Method:x="+x);
}
public static void tripleSalary(Employee2 x) {
x.raiseSalary(200);
System.out.println("End of method:salary="+x.getSalary());
}//因为不需要被外部类使用,所以用static
public static void swap(Employee2 x,Employee2 y) {
Employee2 temp=x;
x=y;
y=temp;
System.out.println("End of method: x="+x.getName());
System.out.println("End of method: y="+y.getName());
}
}
class Employee2
{
private String name;
private double salary;
public Employee2(String n,double s) {
name=n;
salary=s;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void raiseSalary(double byPercent) {
double raise=salary*byPercent/100;
salary+=raise;
}
}
对象构造
27、重载:多个方法有相同的名字、不同的参数,便说是产生了重载。编译器通过用各个方法给出的参数类型和特定方法调用所使用的值类型进行匹配来挑选出相应的方法——重载解析
28、默认域初始化:必须明确地初始化方法中的局部变量。但是,如果没有初始化类中的域,将会被自动初始化为默认值(0、false、null)
29、无参数的构造器:很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值。如果类中提供力至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法【这个时候系统不会自动提供一个无参数构造器】只有当类没有提供任何构造器时,系统才会提供一个默认的构造器
30、显示域初始化:调用类中的方法对域进行初始化
31、参数名:在编写小的构造器时,参数一般由单个字符命名。this指示隐式参数,也就是所构造的对象
32、调用另一个构造器:如果构造器的第一个语句形如this(……)
,这个构造器将调用同一个类的另一个构造器,C++不能这样做。
33、例子:
public class ConstructorTest {
public static void main(String[] args) {
Employee3[] staff=new Employee3[3];
staff[0]=new Employee3("Loan",10000);
staff[1]=new Employee3(6000);
staff[2]=new Employee3();
for(Employee3 e:staff)
System.out.println("姓名:"+e.getName()+", id:"+e.getId()
+", 工资:"+e.getSalary());
}
}
class Employee3
{ //静态域
private static int nextId;
//显示域初始化,在执行构造器之前,先执行赋值操作
//将一个值赋给任何域,当一个类的所有构造器都希望把相同的值赋予一个特定的域时使用
//不一定要初始化为一个变量,也可以调用一个方法
private String name="";
private int id;
private double salary;
//对象初始化块,只要构造类对象,这些块就会被执
//无论使用哪个构造器构造对象,id域都会在对象初始化块中被初始化
//编译器执行时,首先执行初始化块中的代码,之后再执行构造器中的内容
{
id=nextId;
nextId++;
}
//静态初始化块,用于对类的静态域进行初始化
//在类第一次被加载时,都会对静态域进行初始化,这是显示地将静态域进行初始化
static {
Random generator=new Random();
nextId=generator.nextInt(1000);
}
//构造器,因为有这个所以编译器不会自动生成无参构造
public Employee3(String n,double s) {
name=n;
salary=s;
}
//构造器中调用类的另一个构造器,必须放在构造器的第一个语句中
public Employee3(double s) {
this("Employee4 #"+nextId,s);
}
public Employee3() {}//自己弄一个无参构造
public String getName() {
return name;
}
public int getId() {
return id;
}
public double getSalary() {
return salary;
}
}
34、finalize:
Java有自动的垃圾回收器,所以Java不需要人工回收内存,不需要析构器。但是有一些对象使用了内存之外的其他资源,比如文件或者系统资源的另一个对象的句柄,这个时候就需要使用finalize
方法在垃圾回收器清楚对象之前调用。实际应用中,很少使用这个方法,使用时机不好把握。一般情况下,如果不得不在某个资源使用完后由人工来管理关闭,那么就用close
方法来清理。
包
35、使用包的主要原因是确保类名的唯一性,注意对于编译器来说,嵌套的包并没有任何关系
36、import
与C++的#include
并不完全相同,C++编译器无法跑到文件的外面去,所以#include
实际上是把文件的内容搬过来,而Java的import
是告诉编译器去哪个地方找这个文件
37、编译器在编译源文件时不检查目录结构,所以即使在某一目录下没有该源文件,编译器也不会查出错误
38、在UNIX环境中,类路径中的不同项目之间采用冒号分隔;在Windows环境中,则使用分号分隔。类路径=基目录+当前目录+jar文件
39、一个源文件只能定义一个公有类public class,并且公有类名必须与源文件名匹配
类设计
1、保证数据私有,不破坏封装性,需要的时候可以编写访问器getter和更改器setter方法,但是还是得注意保持域的私有性
2、对数据初始化。Java不对局部变量进行初始化,但是会对对象的实例域进行初始化。最好不要依赖于系统的默认值,而是显示的初始化所有的数据,具体的初始化方式可以是提供默认值,也可以是在所有构造器中设置默认值。
3、不要再类中使用过多的基本类型,就是说在类中,尽量使用自定义类或者其他类,而不是int、char等基本数据类型
4、不是所有的域都需要独立的与访问器和域更改器,还是为了保证域的私有性
5、将职责过多的类进行分解
6、注意类的域和方法名要体现其相应的作用
7、优先使用不可变的类。什么是不可变的类—没有什么方法能改变对象的状态,即可以读但是不可以写。计算之后会生成新的值而不是更改原来的值。
不可变的类和可变的类:https://www.cnblogs.com/esther-qing/p/6484965.html
可变类和不可变类
1、可变类:当引用类的一个实例时,可以改变这个实例的内容
2、不可变类:当获得这个类的一个实例引用时,不可以改变这个实例的内容。不可变类的实例一旦被创建,其在成员变量的值就不能被修改
3、自定义不可变类:
①不可变类的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象
②不可变类的所有域都应该是final
③对象必须被正确地创建
④对象应该是final,这样一来就没法作为父类继承了
⑤如果类中包含可变类对象,那么返回給客户端该域的对象时,应该返回该对象的一个拷贝,而不是该对象本身
4、使用不可变类的好处:
①线程安全,可以不需要被synchronized就可以在并发环境*享
②简化了程序开发,无需使用额外的锁机制,如①说的
③提高了程序的性能,还是①
④对象可以被重复使用,将它们缓存起来重复使用,就像字符串字面量
5、不可变类缺点:制造大量的垃圾,由于他们不能被重用而且对于它们的使用就是“用”然后“扔”,字符串srting就是一个典型的例子,它会制造很多垃圾
6、不可变类一个易懂的例子:String
,看下面代码:
String s="ABC";
s.toLowerCase();
以上并没有改变“ABC”的值,而是创建了一个新的String类“abc”,然后变量s指向了新的实例
字符串常量池是Java堆内存中一个特殊的存储区域,当创建一个String对象时,如果此字符串值已经存在于常量池中,就不会创建一个新的对象,而是引用已经存在的对象(将变量指向内存池对应的内存地址)
7、一个问题:Java把String设成immutable最大的原因应该是效率和安全。
但有的时候String的immutable特性也会引起安全问题,这就是密码应该存放在字符数组中而不是String中的原因!
1) 由于String在Java中是不可变的,如果你将密码以明文的形式保存成字符串,那么它将一直留在内存中,直到垃圾收集器把它清除。而由于字符串被放在字符串缓冲池中以方便重复使用,所以它就可能在内存中被保留很长时间,而这将导致安全隐患,因为任何能够访问内存(memory dump内存转储)的人都能清晰的看到文本中的密码,这也是为什么你应该总是使用加密的形式而不是明文来保存密码。由于字符串是不可变的,所以没有任何方式可以修改字符串的值,因为每次修改都将产生新的字符串,然而如果你使用char[]来保存密码,你仍然可以将其中所有的元素都设置为空或者零。所以将密码保存到字符数组中很明显的降低了密码被窃取的风险。
2) Java本身也推荐使用JPasswordField组件的getPassword()方法,该方法将返回一个字符数组,而放弃了原来的getText()方法,这个方法把密码以明文的形式返回而可能会引起安全问题。所以,最好能听从来自Java团队的建议并且坚持标准,而不是去反对它。
3)使用字符串,在将文本输出到日志文件或者控制台的时候会存在风险。但是使用数组你不会把数组的内容打印出来,相反,打印出来的是数组在内存中的位置。尽管这算不上一个真正的原因,但这仍然很有意义。
这就是全部的关于为什么使用字符数组存储密码比字符串更好。只使用字符数组也是不够的,为了更安全你需要将数组内容进行转化。我也建议使用哈希的或者是加密过的密码而不是明文,然后一旦完成验证,就将它从内存中清除掉。
上一篇: 文科生的SQL初体验之模糊查询
下一篇: 数据库原理笔记