Java第二天
今天按照时间表上应该是流程控制和基本运算符,这两部分其实没什么好说的 ,直接开始下一部分的复习。
一、数组
数组基础的内容就不记了。
数组的空指针异常
public static void main(String[] args) {
int[] arr = {1,2,3};
arr = null;
System.out.println(arr[0]);
}
arr = null 这行代码,意味着变量arr将不会在保存数组的内存地址,也就不允许再操作数组了,因此运行的时候 会抛出 NullPointerException 空指针异常。
foreach
for(int element :a)
System.out.println(element);
命令行参数
每一个Java因应用程式都有一个带String arg[] 参数的main方法。这个参数表明main方法将接受一个字符串数组,也就是命令行上指定的参数。
public class Demo {
public static void main(String[] args) {
for (String s : args) {
System.out.println(s);
}
}
}
cmd运行
编译 javac Demo.java
运行: java Demo aaa bbb ccc
参数之间用空格隔开
数组拷贝
在java中允许将一个数组变量拷贝到另一个数组变量。这时,两个变量将引用同一个数组。
如果希望将一个数组的所有值拷贝到一天个新的数组中去,这里有两种方法
- 使用Arrays类的copyOf方法
int[] src = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
int[] dest1 = Arrays.copyOf(src, 7);
//Arrays.copyOf不改变原数组
int[] dest2 = Arrays.copyOf(src, 10);
int[] dest2 = Arrays.copyOf(src, 2*10);//这个方法通常用来增加数组的长度
- arraycopy(Object src,int srcPos,Object dest, int destPos,int length);
将指定源数组中的数组从指定位置复制到目标数组的指定位置。
int[] src = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
int[] dest = new int[3];
//方法二
System.arraycopy(src, 2, dest, 0, 3);
数组排序
使用Arrays类中的sort方法。
这里的sort使用的优化的快排算法。
int [] a =new int [1000];
Arrays.sort(a);
常用API
输出数组
int[] array = { 1, 2, 3 };
System.out.println(Arrays.toString(array));
数组是否包含某个值
int[] array = { 1, 2, 3 };
Arrays.equals(a,2);
查找数组元素
int[] array = { 1, 2, 3 };
int a=Arrays.binarySearch (array1,1,2,2);//数组,start,end,查询的值
填充数组
int[] array = { 1, 2, 3 };
Arrays.fill(array,0;
数组转List
String[] array2 = {"a", "b", "c", "d"};
System.out.println(array2); // [Ljava.lang.String;@13b6d03
List list = new ArrayList(Arrays.asList(array2));
System.out.println(list); // [a, b, c, d]
list.add("GG");
System.out.println(list); // [a, b, c, d, GG]
二、封装
什么是封装
我们程序设计要追求**“高内聚,低耦合”**。高内聚就是类的内部数据操作细节自己完成,不允许外部干涉,低耦合,仅暴露少量的方法给外部使用。比如说我们玩游戏,我们只能看到我们点卷的数量但是我们不能随意改动,我们只能通过特定的接口(充值)增加我们的点卷。我们甚至看不见别人的点卷数量,这就是数据的隐藏。在我学Android开发的时候,类里面的属性都要用Private修饰。
封装的优点
- 提高程序的安全性,保护数据
- 隐藏代码的实现细节
- 统一接口
- 系统可维护性增加了
如何实现封装
在类中通过setter和getter设置属性的获取和修改的接口。(快捷键 alt+insert)
package opp.Deom04;
public class Game {
private int Grade;
private int Money;
public int getGrade() {
return Grade;
}
public void setGrade(int grade) {
Grade = grade;
}
public int getMoney() {
return Money;
}
public void setMoney(int money) {
Money = money;
}
}
package opp.Deom04;
public class Application {
public static void main(String[] args) {
Game gamer1=new Game();
gamer1.setGrade(100);//设置等级和金钱
gamer1.setMoney(200);
System.out.println(gamer1.getGrade());//获取等级和金钱
System.out.println(gamer1.getMoney());
}
}
三、继承
什么是继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模,降低代码编写的冗余度,提高编写的效率。这个世界上有很多人,每个人都有不同的职业,种族等等。如果我们想对每一类人创建一个相对应的类,那么每个类当中都会有一些相同的属性,比如身高,体重等等。那么这样代码失去了简洁的美,我们将这些共同的属性与方法创建在一个类当中,这个类就是父类。那么每个不同种类的人只需要extends(继承)父类,就可以继承父类的所有属性与方法。我们只需要在不同的子类中写只属于这个子类的属性与方法即可。
JAVA继承的特性
- JAVA中只有但继承,没有多继承。也就是说一个子类只能继承一个父类,但是一个类可以实现多个接口。
- 构造方法不会被子类继承,但可以从子类中调用父类的构造方法。
- 子类不能选择性继承父类。
- 子类继承其父类的所有public和protected成员,但不能继承其父类的private成员。
继承的用法
直接使用父类的属性与方法
class Father{
String name ="Father"
public void sayHello() {
System.out.println("Hello,Welcome to Java!!!");
}
public void sayBye() {
System.out.println("GoodBye,everyone");
}
}
class Son extends Father {
}
public class Test {
public static void main(String[] args) {
Son son =new Son();//创建子类的一个实例对象,使用默认构造方法
System.out.println(son.name);
b.sayHello(); //调用子类中重写的方法
b.sayBye(); //调用父类中的方法
}
}
Father
Hello,Welcome to Java!!!
GoodBye,everyone
重写和隐藏父类中的方法
当一个子类中一个实例方法具有与其父类中的一个实例方法相同的名称,参数个数与类型和返回值时,称子类中的方法“重写”了父类的方法。
如果一个子类定义了一个静态类方法,而这个类方法与其父类的一个类方法具有相同的名称,参数个数与类型和返回值,则称在子类中的这个类方法“隐藏”了父类中的该类方法。
class A{
public static void sayHello() { //静态类方法
System.out.println("大家好,这是A的静态类方法");
}
public void sayHello2() { //实例方法
System.out.println("大家好,这是A中的实例方法");
}A
}
class B extends A {
public static void sayHello() { //静态类方法
System.out.println("大家好,这是B的静态类方法");
}
public void sayHello2() { //实例方法
System.out.println("大家好,这是B的实例方法");
}
}
public class myfirst {
public static void main(String[] args) {
B b=new B(); //创建B类的实例对象b
A a=b; //隐式对象类型转换
A.sayHello(); //调用A类的静态类方法
a.sayHello(); //调用a对象的静态类方法
B.sayHello(); //调用B类的静态方法
a.sayHello2(); //调用a对象的实例方法
b.sayHello2(); //调用b对象的的实例方法
A a2=new A(); //创建A类的实例对象a2
a2.sayHello2(); //调用a2对象的实现方法
}
}
重写与隐藏的区别就是在隐式对象型转换的时候调用时候的区别。将子类对象转为父类的时候,调用静态方法会调用父类的,调用实例方法会调用子类的。
大家好,这是A的静态类方法
大家好,这是A的静态类方法
大家好,这是B的静态类方法
大家好,这是B的实例方法
大家好,这是B的实例方法
大家好,这是A中的实例方法
子类重写方法时,其修饰符访问权限允许大于但不允许小于其重写的方法。如果一个方法在父类时static方法,那么子类也必须是static方法,如果一个方法在父类时实例方法,那么在子类中也要是实例方法。
Super的使用
- 调用父类中重写的方法
class Father{
String name ="Father"
public void sayHello() {
System.out.println("Hello,Welcome to Java!!!");
}
public void sayBye() {
System.out.println("GoodBye,everyone");
}
}
class Son extends Father {
public void sayHello() {
super.sayHello();
System.out.println("欢迎来到JAVA!!!!");
}
}
public class Test {
public static void main(String[] args) {
Son son =new Son();//创建子类的一个实例对象,使用默认构造方法
b.sayHello(); //调用子类中重写的方法
}
}
Hello,Welcome to Java!!!
欢迎来到JAVA!!!!
- 使用super调用父类的构造器
class Father{
String name ="Father"
private int money;
Father (){
System.out.println("这是父类SuperClass无参数构造方法");
}
Father (int n){
System.out.println("这是父类SuperClass无参数构造方法");
this.money=n;
}
}
class Son extends Father {
private int money;
Son (){
System.out.println("这是子类无参数构造方法");
}
Son (int n){
super(n);//父类的有参构造器需要显式
System.out.println("这是子类无参数构造方法"+n);
this.money=n;
}
}
public class Test {
public static void main(String[] args) {
Son son =new Son();
Son son1 =new Son(10086);
}
}
- 如果父类没有无参构造器,只有有参构造器,那么子类中也不能有无参构造器,只能由有参构造器。
- 如果要初始化父类中的字段,可以在子类的构造方法中通过关键字super调用父类的构造方法;
- 子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)
- 如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器;
- 子类构造器中this和super都要放在第一行,所以this和super不能一起用。
四、多态
什么是多态
在学习继承的时候就为多态做了铺垫,所谓多态就是指向一个实例可以引用不同的类,从而实现代码的灵活性与可维护性。即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态。因为该变量到底会引用哪个类的方法必须由程序运行期间才能决定。
这么说可能还是不好理解,因为我也不知道我在说什么,直接上代码。
public class Person{
public void interest1() {
System.out.println("吃吃吃!!!");
interest2();
}
public void interest2() {
System.out.println("喝喝喝!!!");
}
public void interest3() {
System.out.println("睡睡睡!!!");
}
}
public class Student extends Person {
/**
子类重载父类方法
父类中不存在该方法,向上转型后,父类是不能引用该方法的
*/
public void interest1(String food) {
System.out.println("吃"+food);
interest2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用 interest2时,必定是调用该方法
*/
public void interest2() {
System.out.println("我爱学习,学习爱我!我就是学生");
}
public void interest4() {
System.out.println("逛B站!!!");
}
}
public class Test {
public static void main(String[] args) {
Student s1= new Student();
Person s2=new Student();
s2.interest1();
System.out.println("没有被子类重写或者重载的方法========================");
s1.interest3();
s2.interest3();
System.out.println("子类独有的方法,s2无法使用,需要强制转换成子类才能使用(高转低)");
((Student) s2).interest4();
}
}
结果
----------------------------------------------------------------------------------
吃吃吃!!!
我爱学习,学习爱我!我就是学生
没有被子类重写或者重载的方法========================
睡睡睡!!!
睡睡睡!!!
子类独有的方法,s2无法使用,需要强制转换成子类才能使用(高转低)
逛B站!!!
分析:
首先我们在父类Person中写了三个方法,interest1,interest2和interest3(对应吃 喝 睡)。在子类学生中重载了interest1这个方法,重写了interest2这个方法,并且写了子类独有的interest4这个方法。s2是指向子类的父类引用,我们调用s2的interest1方法时,重载后的interest1(String food) 与interest1()不是一个方法,所以第一行输出的是父类的”吃吃吃!!!“,接下来在interest1中调用interest2方法,由于子类重写了该方法,那么指向Student 的Person引用就会调用Student中interest2()方法。
那么在调用没有被子类重写或者重载的方法,毫无疑问都是父类的方法。
子类独有的方法interest4(),s2无法使用,需要强制转换成子类才能使用(高转低)。
((Student) s2).interest4();
这么一段的分析,读个一两遍也就能理解了。
总结:
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载父类的方法也不可以。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法。
在理解了多态的概念之后我们怎么实现多态呢?
如何实现
学习继承就已经为多态做好了准备。我们可以编写一个指向子类的父类引用。例如 Father s=new Son(); 这样该引用就可以处理父类对象,也可以处理子类对象。程序就有了多种运行状态,该对象就会根据自己所属的引用而执行不同的行为,这就是多态。
实现的条件
- 继承: 在多态中必须存在继承关系的子类和父类。
- 重写: 子类对父类的某些方法进行重写,这样在调用指向子类的父类引用时就会调用子类的方法。并且从这个条件我们可以知道多态是对方法的多态,属性和静态方法是无法重写的。
- 向上转型: 我们需要构造一个指向子类的父类引用。
实现方式
基于继承的多态
package opp;
public class Person{
private String name ;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public Person() {
}
public void interest2() {
System.out.println("喝喝喝!!!");
}
@Override
public String toString() {
return getName();
}
}
public class Student extends Person {
public Student() {
setName("Student");
}
public void interest2() {
System.out.println("我爱学习,学习爱我!我就是学生");
}
public String toString(){
return getName();
}
}
public class Test {
public static void main(String[] args) {
Person s1=new Person();
Person s2=new Person();
Student a1= new Student();
Programmer a2=new Programmer();
s1=a1;
s2=a2;
s1.interest2();
s2.interest2();
}
}
结果
-------------------------------------------------------
我爱学习,学习爱我!我就是学生
Coding!!!
总结:对于Person这个父类,我们设置了两个不同的子类,并且重写了父类的方法。这就意味着我们处理引用子类的父类类型,子类对象的不同,重写的方法也就不同,执行相同动作而产生的行为也不同。并且它适用于继承该父类的所有子类
基于接口实现的多态
这个跟基于继承的多态类似,就不详细展开了,下面放一个“迷魂阵”,大家来理解理解
实例:
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
结果
-------------------------------------------------
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
①②③比较好理解,一般不会出错。④⑤就有点糊涂了,为什么输出的不是"B and B”呢?!!先来回顾一下多态性。
运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态方法调度,它是一种在运行时而不是在编译期调用重载方法的机制。
方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。 (但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)
好了,先温习到这里,言归正传!实际上这里涉及方法调用的优先问题 ,优先级由高到低依次为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。让我们来看看它是怎么工作的。
比如④,a2.show(b),a2是一个引用变量,类型为A,则this为a2,b是B的一个实例,于是它到类A里面找show(B obj)方法,没有找到,于是到A的super(超类)找,而A没有超类,因此转到第三优先级this.show((super)O),this仍然是a2,这里O为B,(super)O即(super)B即A,因此它到类A里面找show(A obj)的方法,类A有这个方法,但是由于a2引用的是类B的一个对象,B覆盖了A的show(A obj)方法,因此最终锁定到类B的show(A obj),输出为"B and A”。
再比如⑧,b.show©,b是一个引用变量,类型为B,则this为b,c是C的一个实例,于是它到类B找show(C obj)方法,没有找到,转而到B的超类A里面找,A里面也没有,因此也转到第三优先级this.show((super)O),this为b,O为C,(super)O即(super)C即B,因此它到B里面找show(B obj)方法,找到了,由于b引用的是类B的一个对象,因此直接锁定到类B的show(B obj),输出为"B and B”。
按照上面的方法,可以正确得到其他的结果。
问题还要继续,现在我们再来看上面的分析过程是怎么体现出蓝色字体那句话的内涵的。它说:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。还是拿a2.show(b)来说吧。
a2是一个引用变量,类型为A,它引用的是B的一个对象,因此这句话的意思是由B来决定调用的是哪个方法。因此应该调用B的show(B obj)从而输出"B and B”才对。但是为什么跟前面的分析得到的结果不相符呢?!问题在于我们不要忽略了蓝色字体的后半部分,那里特别指明:这个被调用的方法必须是在超类中定义过的,也就是被子类覆盖的方法。B里面的show(B obj)在超类A中有定义吗?没有!那就更谈不上被覆盖了。实际上这句话隐藏了一条信息:它仍然是按照方法调用的优先级来确定的。它在类A中找到了show(A obj),如果子类B没有覆盖show(A obj)方法,那么它就调用A的show(A obj)(由于B继承A,虽然没有覆盖这个方法,但从超类A那里继承了这个方法,从某种意义上说,还是由B确定调用的方法,只是方法是在A中实现而已);现在子类B覆盖了show(A obj),因此它最终锁定到B的show(A obj)。这就是那句话的意义所在。
原文:https://blog.csdn.net/thinkGhoster/article/details/2307001
面向对象三大特性
总结:到这里我们面向对象的三大特性就学完了。
面向对象编程的本质就是:以类的方式组织代码,以对象的方式组织(封装数据)。
这些都是抽象的,从封装到继承再到多态,这些知识越来越抽象,这也是编程重要思想。我们的代码越来越简洁,可维护性越来越好,但这些都是靠自身的学习才能实现的。
五、static详解
那么在学习更抽象的抽象类与接口之前,我们先学习一下static缓解缓解。
静态属性与方法
使用static修饰的属性或者方法我们可以直接调用类来使用,不需要构造一个对象再通过对象来使用。
public class Static {
private static int age=10;//静态变量,在调用时可以不用实例化
private double score;//非静态变量
public static void main(String[] args) {
Static student =new Static();
System.out.println(student.age);
System.out.println(age);
System.out.println(student.score);
student.go();
run();
}
public void go(){
System.out.println("GO!!!");
}
public static void run(){ //静态方法
System.out.println("RUN!!!!");
}
}
静态代码块
package opp;
public class Demo07 {
{
System.out.println("我是匿名代码块!!");
}
static{
System.out.println("我是静态代码块!!!");
}
public Demo07() {
System.out.println("构造方法!!");
}
public static void main(String[] args) {
Demo07 d1=new Demo07();
System.out.println("========================================");
Demo07 d2=new Demo07();
}
}
结果
-------------------------------------------------------------
我是静态代码块!!!
我是匿名代码块!!
构造方法!!
========================================
我是匿名代码块!!
构造方法!!
从这里我们可以看到,静态代码块是在类创建的时候一起执行的,所以最先执行的就是静态代码块,并且只会执行一次。匿名代码块是随着对象的构造一起执行的,所以每次构造对象就会执行一次,并且比构造器先执行。
六、抽象类
抽象类我们使用的并不多,我们了解一下即可,重要的是接口。我们先了解一下什么是抽象类。假设我们做一款游戏,游戏里有许多角色,但是这个角色非常复杂,我们要反复创建他的话就会很麻烦,我们就可以把他的一些功能给抽象出来一个父类。这样每创建一个角色就可以继承这个抽象类,实现父类的方法再写一些自己的特有的东西,这样一个角色就创建好了。说到底还是为了代码的简洁,道理跟继承是相似的。
抽象类的特点
- 凡是用abstract 修饰符修饰的类被称为抽象类。凡是用abstract修饰符修饰的成员方法被称为抽象方法。
- 抽象类也是单继承,接口可以实现多继承
- 抽象方法必须在抽象类中,抽象类可以写普通类,不一定要有抽象类
- 抽象类不能创建对象,如果子类实现了抽象类的所有方法,那么这个子类可以创建对象,否则这个子类还是抽象类。
- abstract不能与final并列修饰同一个类。 abstract 不能与private、static、final或native并列修饰同一个方法。
代码实现
public abstract class Role {
private int hp=1000;
public abstract void Skill01();
public abstract void Skill02();
public int Hp(int n){
return (hp+n);
};
}
public class Fiora extends Role {
@Override
public void Skill01() {
System.out.println("决斗之舞");
}
@Override
public void Skill02() {
System.out.println("夺命连刺");
}
public static void main(String[] args) {
Fiora f1=new Fiora();
f1.Skill01();
System.out.println(f1.Hp(10));
}
}
结果
-----------------------------------------------------
决斗之舞
1010
七、接口
普通类:只有具体的实现
抽象类:具体实现和规范都有
接口:只有规范,自己无法写方法。约束和实现分离。定义的是一组规则,在现实生活中就是“如果你是xxx,你必须xxxx” 的思想。如果你是天使,你必须能飞。接口的本质是契约,就像我们人间的法律一样,制定好之后必须要遵守。
接口的定义
接口使用interface来定义一个接口。基本格式如下:
[修饰符] interface 接口名 [extends 父接口名列表]{//修饰符可选public,省略则为默认访问权限
[public] [static] [final] 常量;
[public] [abstract] 方法;
}
接口中的方法默认的都是public 和abstract
接口中的属性 public static final
接口的实现类
接口不能被实例化 接口中没有构造方法,所以接口都需要实现类。这个实现类中实现离接口中的所有方法,也就是说要遵守接口的规范。
定义接口时,如果关键字interface前面加上public关键字,就称这样的接口是一个public接口,则可以被任何一个类实现,如果一个接口不加public修饰,就称作友好接口,友好接口可以被与该接口在同一包中的类实现。
基本格式如下:
[修饰符] class <类名> [extends 父类名] [implements 接口列表]{}
public interface Jiekou {
// 接口中的所有定义都是抽象的
void run();
public abstract void eat();
//接口中的属性都是常量
public static final int age=99;
}
public interface Jiekou2 {
void timeServe();
}
//继承多个接口,各个接口名之间使用逗号分隔。
public class jiekouImpl implements Jiekou,Jiekou2 {
@Override
public void run() {
System.out.println("快逃!!!");
}
@Override
public void eat() {
System.out.println("别吃别吃别吃");
}
@Override
public void timeServe() {
System.out.println("我起飞了");
}
public static void main(String[] args) {
jiekouImpl l1=new jiekouImpl();
l1.run();
l1.eat();
l1.timeServe();
}
}
分析:我们定义了两个接口,分别有run() eat()和timeServe的方法。我们在实现类上继承了这个两个接口,并重写他们所有的方法。这样我们就可以实例化一个对象,并且使用其中的方法。