控制与输入输出

新的for循环

1
2
3
4
for(声明语句 : 表达式) 
{
//代码句子
}

声明语句:声明新的局部变量,该变量的类型必须和数组元素的类型匹配。其作用域限定在循环语句块,其值与此时数组元素的值相等。

表达式:表达式是要访问的数组名,或者是返回值为数组的方法。

print()输出完毕后不换行,而println()输出完毕后会换行

这个区别主要体现在新的for循环里面

1
2
3
4
5
6
7
8
9
10
11
12
int [] numbers = {10, 20, 30, 40, 50};

for(int x : numbers ){
System.out.print( x );
System.out.print(",");
}
System.out.print("\n");
String [] names ={"James", "Larry", "Tom", "Lacy"};
for( String name : names ) {
System.out.print( name );
System.out.println(",");
}

输出为

1
2
3
4
5
10,20,30,40,50,
James,
Larry,
Tom,
Lacy,

输入

Ⅰ. 用法

引入 import java.util.Scanner;

1
2
Scanner scan = new  Scanner(System.in);
<赋值给谁?>=sc.nextLine();

Ⅱ .next() 与 nextLine() 区别

next():

  • 一定要读取到有效字符后才可以结束输入。
  • 对输入有效字符之前遇到的空格,next() 方法会自动将其去掉。
  • 只有输入有效字符后才将其后面输入的空格作为分隔符或者结束符。
  • next() 不能得到带有空格的字符串。

nextLine():

  • 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  • 可以获得空。

Ⅲ.进一步

1
2
3
4
5
6
7
Scanner scan = new  Scanner(System.in);
char grade = scan.next().charAt(0);
//读取第一个字符

scan.nextInt();//读取第一个整数
scan.nextDouble();
scan.nextline();//读取整个字符串

方法与类

extends继承

对于一些类,我们常常有相同的变量,比如

1
2
图书:价格 编号 颜色
文具:价格 样式 颜色

那么我们可以

1
2
3
4
5
6
7
8
9
10
11
12
13
class Item{
int price;
int color;
}
public class Book extends Item{
int num;
}
//等价于
public class Book extends Item{
int num;
int price;
int color;
}

这样Book就继承了Item的定义的数据,也就是Book有自身的num,也有Item的price和color

一个类只能继承一个类,一个类可以被多个类继承!

子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

protected关键字

继承有个特点,就是子类无法访问父类的private字段或者private方法。

例如,Student类就无法访问Person类的nameage字段:

为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
private String name;
private int age;
}

class Student extends Person {
public String hello() {
return "Hello, " + name; // 编译错误:无法访问name字段
}
}

//--------------- V S -----------------


class Person {
protected String name;
protected int age;
}

class Student extends Person {
public String hello() {
return "Hello, " + name; // OK!
}
}

创建对象用构造方法

用来创建对象的方法叫做构造方法

其基本格式是方法名和类名一样(包括大小写)&&没有返回类型!!!!!!!!!!

1
2
3
4
5
// 方法名和类名一样(包括大小写)
// 没有返回类型!!!!!!!!!!
public Hero() {
System.out.println("实例化一个对象的时候,必然调用构造方法");
}

我们常常使用

ADHero bh = new ADHero();来构造一个对象,可是我们并没有设置ADHero()这个方法

Java帮助我们自动设定了一个方法,我们也可以自定义

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

String name; //书名

int price; //价格

//可以构造有参的方法,在构造时就进行赋值或输出等操作
//有参的构造方法
//默认的无参的构造方法就失效了
public Hero(String bookname){
name = bookname;
System.out.println("构造成功!");
}

public static void main(String[] args) {
Hero garen = new Hero("汤姆索亚历险记");

//Hero teemo = new Hero();
//若不注释此句,则会报错,因为有了你自己定义的有参方法,无参的构造方法“木有了”
}
}

构造方法允许使用重载

注意:引用变量的声明类型可能与其实际类型不符,例如(student为person的子类)

1
Person p = new Student();

注意:可以有以类为元素的数组,初始化数组时即直接new(A、B、C、Income为4个类)

1
2
3
4
5
Income[] incomes = new Income[] {
new A(3000),
new B(7500),
new C(15000)
};

this的使用

this就是代指我(当前对象)

常见使用

  • 通过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
public class Book2 {

String name; //姓名
float hp; //余量
int price;

//带一个参数的构造方法
public Book2(String name){
System.out.println("AAA");
this.name = name;//通过this关键字访问对象的属性
}

//带两个参数的构造方法
public Book2(String name,float hp){
this(name);//相当于调用Book2(String name) 【根据参数数量匹配】
System.out.println("BBB");
this.hp = hp;//通过this关键字访问对象的属性
}
public void setPrice(int pri){
this.price=price//等价于 price=pri
}

public static void main(String[] args) {
Book2 teemo = new Book2("提莫",383);
System.out.println(teemo.name);

}

}

输出

1
2
3
AAA
BBB
teemo

函数传参数

基本类型(如 int、float、bool)传入拷贝,不改外值

类类型(含 String)传入地址,指针改变。

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

String name; //姓名
float hp; //血量
public Hero(String name,float hp){
this.name = name;
this.hp = hp;
}
//复活
public void revive(Hero h){
h = new Hero("提莫",383);
}
public static void main(String[] args) {
Hero teemo = new Hero("提莫",383);
//受到400伤害,挂了
teemo.hp = teemo.hp - 400;

teemo.revive(teemo);
//问题: System.out.println(teemo.hp); 输出多少? 怎么理解?
}
}

答案: -17

原因:这个练习不同在于,main方法中的teemo引用与revive方法中的teemo引用不是一个引用。revive中用构造方法重新生成了一个teemo引用。main方法中还是调用的原来的引用(相当于main函数中先malloc了一次,再在revive函数里面malloc一次),所以结果为-17,没有在函数里面赋值。

尽量在main函数里面new

再如:

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
public class Dog {
String name;

Dog(String name) {
this.name = name;
}

String getName() {
return this.name;
}

void setName(String name) {
this.name = name;
}

String getObjectAddress() {
return super.toString();
}
}
public class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.getObjectAddress()); // Dog@4554617c
func(dog);
System.out.println(dog.getObjectAddress()); // Dog@4554617c
System.out.println(dog.getName()); // A
}

private static void func(Dog dog) {
System.out.println(dog.getObjectAddress()); // Dog@4554617c
dog = new Dog("B");
System.out.println(dog.getObjectAddress()); // Dog@74a14482
System.out.println(dog.getName()); // B
}
}

可以看到经过了 func(dog)但没有把新的dog B给dog A

​ Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。

但是如果改变对象的字段值会改变源对象字段值

1
2
3
4
5
6
7
8
9
10
11
12
class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}

private static void func(Dog dog) {
dog.setName("B");
}
}

方法的重载

对于某东西,可能攻击一个,也可能攻击多个

1
2
3
4
public void attack()
public void attack(Hero h1)
public void attack(Hero h1, Hero h2)
//……

那么,在调用方法attack的时候 ,会根据传递的参数类型以及数量,自动调用对应的方法

方法名字可以一样

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
public class ADHero extends Hero {
public void attack() {
System.out.println(name + " 进行了一次攻击 ,但是不确定打中谁了");
//这里的第一个name就是指这个class的name!!!!!!
}
public void attack(Hero h1) {
System.out.println(name + "对" + h1.name + "进行了一次攻击 ");
//这里的第一个name就是指这个class的name!!!!!!
}
public void attack(Hero h1, Hero h2) {
System.out.println(name + "同时对" + h1.name + "和" + h2.name + "进行了攻击 ");
//这里的第一个name就是指这个class的name!!!!!!
}

public static void main(String[] args) {
ADHero bh = new ADHero();
bh.name = "赏金猎人";
Hero h1 = new Hero();
h1.name = "盖伦";
Hero h2 = new Hero();
h2.name = "提莫";

bh.attack();
bh.attack(h1);
bh.attack(h1, h2);
}

}

输出如下

1
2
3
赏金猎人 进行了一次攻击 ,但是不确定打中谁了
赏金猎人对盖伦进行了一次攻击
赏金猎人同时对盖伦和提莫进行了攻击

对于不确定数量的参数可以用升级的for(可变数量参数

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 ADHero extends Hero {



// 可变数量的参数
public void attack(Hero... heros) {

/*参数写法等同于传了一个数组!但是,如果写成
public void attack(Hero [] heros)
那么传入参数必须为数组!不能像下面bh.attack(h1, h2)这样传*/

for (int i = 0; i < heros.length; i++) {
System.out.println(name + " 攻击了 " + heros[i].name);
}
}

public static void main(String[] args) {
ADHero bh = new ADHero();
bh.name = "赏金猎人";
Hero h1 = new Hero();
h1.name = "盖伦";
Hero h2 = new Hero();
h2.name = "提莫";
bh.attack(h1, h2);

}
}

输出

1
2
赏金猎人 攻击了 盖伦
赏金猎人 攻击了 提莫

方法的重写(覆写)

什么是真正的覆写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
public void run() { /*代码1*/ }
}

class Student extends Person {
// 不是覆写,因为参数不同:
public void run(String s) {/*代码2*/}
// 不是覆写,因为返回值不同:严谨一些:返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
public int run() {/*代码3*/}
// 是覆写:
public void run() { /*代码4*/}

//……
}

对于代码2,程序只需判定他的参数是否符合即可。对于代码3/4程序如何判定呢?请看下面这个例子

覆写的实际例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
}
}

以上实例编译运行结果如下:

1
2
动物可以移动
狗可以跑和走

我们发现:尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。

编译阶段,只是检查参数的引用类型。

运行时,Java 虚拟机指定对象的类型并且运行该对象的方法

因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

请务必区分编译和运行的差异!!!!!!!

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
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
System.out.println("狗可以跑和走");
}
public void bark(){
System.out.println("狗可以吠叫");
}
}

public class TestDog{
public static void main(String args[]){
Animal a = new Animal(); // Animal 对象
Animal b = new Dog(); // Dog 对象

a.move();// 执行 Animal 类的方法
b.move();//执行 Dog 类的方法
b.bark();//编译出现错误!!!!
}
}

以上实例编译运行结果如下:

1
2
3
4
5
TestDog.java:30: cannot find symbol
symbol : method bark()
location: class Animal
b.bark();
^

该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。

super关键字

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}

class Dog extends Animal{
public void move(){
super.move(); // 应用super类的方法
System.out.println("狗可以跑和走");
}
}

public class TestDog{
public static void main(String args[]){

Animal b = new Dog(); // Dog 对象
b.move(); //执行 Dog类的方法

}
}

以上实例编译运行结果如下:

1
2
动物可以移动
狗可以跑和走

也就是先运行父类方法,再运行子类方法。

同理,子类引用父类的字段时,可以用super.fieldName。大多数情况下,使用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
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}

class Person {
protected String name;
protected int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

class Student extends Person {
protected int score;

public Student(String name, int age, int score) {
this.score = score;
}
}
//编译错误,大意是在Student的构造方法中,无法调用Person的构造方法。

因为在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();,所以,Student类的构造方法实际上是这样:

1
2
3
4
5
6
7
8
class Student extends Person {
protected int score;

public Student(String name, int age, int score) {
super(); // 自动调用父类的构造方法
this.score = score;
}
}

但是,Person并没有无参数的构造方法,因此,编译失败。

解决方法是调用Person类存在的某个构造方法。例如:

1
2
3
4
5
6
7
8
class Student extends Person {
protected int score;

public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
final关键字

final 类似于常量const,只能初始化一次

如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
protected String name;
public final String hello() {
//↑标记了final:compile error: 不允许覆写
return "Hello, " + name;
}
}

Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}

如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承:

1
2
3
4
5
6
7
8
final class Person {
//↑标记了final:compile error: 不允许覆写
protected String name;
}

// compile error: 不允许继承自Person
Student extends Person {
}

对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。例如:

1
2
3
4
5
6
7
class Person {
public final String name = "Unamed";
// ↑标记了final:compile error: 不允许覆写
}
//……
Person p = new Person();
p.name = "New Name"; // compile error!

但可以在构造方法中初始化final字段:

1
2
3
4
5
6
class Person {
public final String name;
public Person(String name) {
this.name = name;//√:允许
}
}

多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run(); // 应该打印Person.run还是Student.run?
}
}

class Person {
public void run() {
System.out.println("Person.run");
}
}

class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}

我们由上一个知识点容易知道上述代码的运行结果是:Student.run

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。

多态实例
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
57
58
59
/*计算税率(给一个有普通收入、工资收入和享受国务院特殊津贴的小伙伴算税)*/
public class Main {
public static void main(String[] args) {
// 以类为数组元素的数组(数组内是初始化数组)
Income[] incomes = new Income[] {
new Income(3000),
new Salary(7500),
new StateCouncilSpecialAllowance(15000)
};
System.out.println(totalTax(incomes));
}

public static double totalTax(Income... incomes) {
double total = 0;
for (Income income: incomes) {
total = total + income.getTax();
//这里程序会根据多态自动调用某一个具体的getTax
}
return total;
}
}
//---------------------------------------------------------
class Income {
protected double income;

public Income(double income) {
this.income = income;
}

public double getTax() {
return income * 0.1; // 税率10%
}
}

class Salary extends Income {
public Salary(double income) {
super(income);
}

@Override
public double getTax() {
if (income <= 5000) {
return 0;
}
return (income - 5000) * 0.2;
}
}

class StateCouncilSpecialAllowance extends Income {
public StateCouncilSpecialAllowance(double income) {
super(income);
}

@Override
public double getTax() {
return 0;
}
}

抽象类

Person类派生的StudentTeacher都可以覆写run()方法。

那么父类Personrun()方法没有实际意义,但是我们不能啥也不写或者只写声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
public void run() { … }//正确编译
public void run(); // Compile Error!
//不实现run(啥也不写)也是错误的
}

class Student extends Person {
@Override
public void run() { … }
}

class Teacher extends Person {
@Override
public void run() { … }
}

正确的做法是使用abstract关键字

1
2
3
abstract class Person {
public abstract void run();
}

把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。

必须把Person类本身也声明为abstract,才能正确编译

因为为抽象类,所以new是错误的

1
Person p = new Person(); // 编译错误

其多用于被继承

接口

1
2
3
4
abstract class Person {
public abstract void run();
public abstract String getName();
}

可以被改写为

1
2
3
4
interface Person {
void run();
String getName();
}

interface:比抽象类还要抽象的纯抽象接口,因为它连字段(如public)都不能有。接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)

implements关键字

当一个具体的class去实现一个interface时,需要使用implements关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + " run");
}
@Override
public String getName() {
return this.name;
}
}

在Java中,一个类只能继承自另一个类,不能从多个类继承。

但是,一个类可以实现多个interface,例如:

1
2
3
class Student implements Person, Hello { // 实现了两个interface
...
}

常见错误

属性和属性名一样

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
public class Hero {

String name; //姓名

float hp; //血量

float armor; //护甲

int moveSpeed; //移动速度

//参数名和属性名一样
//在方法体中,只能访问到参数name
public void setName1(String name){
name = name;
System.out.println(h.name);
}

//为了避免setName1中的问题,参数名不得不使用其他变量名
public void setName2(String heroName){
name = heroName;
}

//通过this访问属性
public void setName3(String name){
//name代表的是参数name
//this.name代表的是属性name
this.name = name;
}

public static void main(String[] args) {
Hero h =new Hero();

h.setName1("teemo");
System.out.println(h.name);

h.setName2("garen");
System.out.println(h.name);

h.setName3("sige");
System.out.println(h.name);
}

}

输出为(注意setName1()也有输出)

1
2
3
4
null
null
garen
sige

说明setName1()出错了

属性和属性名一样;在方法体中,只能访问到参数name

建议用 this.name=name这种写法