Java 學習記錄49 — Inner classes

張小雄
7 min readMar 30, 2021

今天學的是 Inner classes

簡單的說就是 class 寫在另一個 class 裡面

GearBox.java

import java.util.ArrayList;public class GearBox {    private ArrayList<Gear> gears;
private int maxGears;
private int currentGear = 0;
public GearBox(int maxGears) {
this.maxGears = maxGears;
this.gears = new ArrayList<>();
Gear neutral = new Gear(0, 0.0);
this.gears.add(neutral);
}
public class Gear {
private int gearNumber;
private double ratio;
public Gear(int gearNumber, double ratio) {
this.gearNumber = gearNumber;
this.ratio = ratio;
}
public double driveSpeed(int revs) {
return revs * this.ratio;
}
}
}

下方的 class Gear 是寫在 class GearBox 裡面

這就是 Inner classes

MainGearBox.java

public class MainGearBox {
public static void main(String[] args) {
GearBox mcLaren = new GearBox(6);
GearBox.Gear first = mcLaren.new Gear(1, 12.3);
// GearBox.Gear second = new GearBox.Gear(2,2.34);
// GearBox.Gear third = new mcLaren.Gear(3, 3.45);
System.out.println(first.driveSpeed(1000));
}
}

輸出結果:

12300.0

要注意的是 如何調用 Gear

先創了一個外部 GearBox 的 instance

接著使用此 instance 來 new 內部的 Gear

註解的兩行代碼就是不知道的人可能會用的方式

那兩行是會報錯的 無法那樣做

GearBox.java

import java.util.ArrayList;public class GearBox {    private ArrayList<Gear> gears;
private int maxGears;
private int currentGear = 0;
private boolean clutchIsIn;
public GearBox(int maxGears) {
this.maxGears = maxGears;
this.gears = new ArrayList<>();
Gear neutral = new Gear(0, 0.0);
this.gears.add(neutral);
}
public void operateClutch(boolean in) {
this.clutchIsIn = in;
}
public void addGear(int number, double ratio) {
if ((number > 0) && (number <= this.maxGears)) {
this.gears.add(new Gear(number, ratio));
}
}
public void changeGear(int newGear) {
if ((newGear >= 0) && (newGear < this.gears.size()) && this.clutchIsIn) {
this.currentGear = newGear;
System.out.println("Gear " + newGear + " selected.");
} else {
System.out.println("Grind ! (gee gee gee sound)");
this.currentGear = 0;
}
}
public double wheelSpeed(int revs){
if(clutchIsIn){
System.out.println("Scream!!");
return 0.0;
}
return revs * this.gears.get(this.currentGear).getRatio();
}
private class Gear {
private int gearNumber;
private double ratio;
public Gear(int gearNumber, double ratio) {
this.gearNumber = gearNumber;
this.ratio = ratio;
}
public double driveSpeed(int revs) {
return revs * this.ratio;
}
public double getRatio() {
return ratio;
}
}
}

把內部的 Gear 從 public 改成 private

GearBox 則新增了 4 個 Method

用意是不讓使用者對內部 Gear 直接操作

像是

GearBox mcLaren = new GearBox(6);
GearBox.Gear first = mcLaren.new Gear(1, 12.3);

不能再使用了

改到 GearBox 的 addGear 去實現了

MainGearBox.java

public class MainGearBox {
public static void main(String[] args) {
GearBox mcLaren = new GearBox(6);
mcLaren.addGear(1,5.5);
mcLaren.addGear(2,10.10);
mcLaren.addGear(3,15.15);
mcLaren.operateClutch(true);
mcLaren.changeGear(1);
mcLaren.operateClutch(false);
System.out.println(mcLaren.wheelSpeed(1000));
mcLaren.changeGear(2);
System.out.println(mcLaren.wheelSpeed(3000));
mcLaren.operateClutch(true);
mcLaren.changeGear(3);
mcLaren.operateClutch(false);
System.out.println(mcLaren.wheelSpeed(6000));
}
}

輸出結果:

Gear 1 selected.

5500.0

Grind ! (gee gee gee sound)

0.0

Gear 3 selected.

90900.0

模擬汽車的換檔操作,引擎齒輪的變更

不使用 mcLaren.addGear(1, 5.5); 來創立 Gear

public GearBox(int maxGears) {
this.maxGears = maxGears;
this.gears = new ArrayList<>();
Gear neutral = new Gear(0, 0.0);
this.gears.add(neutral);
for (int i = 0; i < maxGears; i++) {
addGear(i, i * 5.3);
}
}

改寫在 GearBox 的 constructer

public class MainGearBox {
public static void main(String[] args) {
GearBox mcLaren = new GearBox(6);
mcLaren.operateClutch(true);
mcLaren.changeGear(1);
mcLaren.operateClutch(false);
System.out.println(mcLaren.wheelSpeed(1000));
mcLaren.changeGear(2);
System.out.println(mcLaren.wheelSpeed(3000));
mcLaren.operateClutch(true);
mcLaren.changeGear(3);
mcLaren.operateClutch(false);
System.out.println(mcLaren.wheelSpeed(6000));
}
}

輸出結果:

Gear 1 selected.

5300.0

Grind ! (gee gee gee sound)

0.0

Gear 3 selected.

95399.99999999999

Button.java

import javax.swing.text.View;public class Button {
private String title;
private OnClickListener onClickListener;
public Button(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public void onClick() {
this.onClickListener.onClick(this.title);
}
public interface OnClickListener {
public void onClick(String title);
}
}

MainButton.java

import java.util.Scanner;public class MainButton {
private static Scanner scanner = new Scanner(System.in);
private static Button btn = new Button("Print");
public static void main(String[] args) {
class ClickListener implements Button.OnClickListener {
public ClickListener() {
System.out.println("I've been attached.");
}
@Override
public void onClick(String title) {
System.out.println(title + " was clicked.");
}
}
btn.setOnClickListener(new ClickListener());
listen();
}
private static void listen() {
boolean quit = false;
while (!quit) {
int choice = scanner.nextInt();
scanner.nextLine();
switch (choice) {
case 0:
quit = true;
break;
case 1:
btn.onClick();
break;
}
}
}
}

輸出結果:

I’ve been attached.

1

Print was clicked.

1

Print was clicked.

1

Print was clicked.

0

這邊展示的是 local class

模擬程式與用戶交互

監聽鍵盤的輸入

public static void main(String[] args) {
// class ClickListener implements Button.OnClickListener {
//
// public ClickListener() {
// System.out.println("I've been attached.");
// }
//
// @Override
// public void onClick(String title) {
// System.out.println(title + " was clicked.");
// }
// }
btn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(String title) {
System.out.println(title + " was clicked.");
}
});
listen();
}

這個則是 anonymous class

功能跟註銷掉的差不多

把 class 寫到參數裡面去

但變太複雜而我也看不懂 Tim 講這段時在說啥

How to Understand the Button Code Tutorial for Beginners

Joseph J

Here are my comments, line by line, to better understand the Button Code Tutorial. I agree with all the others that this section should be rerecorded for beginners. If I understand correctly, the purpose of the Button Code section was to understand that you can use local classes in the main method, on the fly, without having to create new files. Unfortunately the concept of local classes was discussed after presenting the concepts of using interfaces as parameters, nesting interfaces within classes, and implementing nested interfaces, concepts which are very difficult for us beginners to understand. Here is my commentary, which is open to correction:

Starting with the Button Class:

public class Button {
//attributes
private String title;
private IOnClickListener onClickListenerVariable;

First of all, I renamed Tim’s OnClickListener interface and his OnClickListener variable. As mentioned in this forum, OnClickListener is not a class, it is an interface. I rename it here on out so we don’t get confused.

By using the IOnClickListener interface next to the onClickListenerVariable above, we are saying that we want the Button class to have an object which has theIOnClickListener interface. When it comes time to assigning a value to onClickListenerVariable, onClickListenerVariable needs to equal (or point to) an object with an implemented IOnClickListener interface.

//constructor
public Button(String title) {
this.title = title;
}

//getter
public String getTitle() {
return title;
}

//setter
public void setOnClickListenerVariable(IOnClickListener onClickListenerVariable){
this.onClickListenerVariable = onClickListenerVariable;
}

The constructor and getter is simple, but the setter is a bit more tricky. Here we have a method which uses an interface as a parameter. We have not yet created this interface, but we will in a moment. Whenever you use an interface as a parameter, you have choices when it comes time to put in an argument:

1.Your argument can be an object from a class that implemented the interface.

For example: setOnclickListenerVariable(myOnClickVariable) only if myOnclickVariable was created from a class that has implemented IOnClickListener

2.Your argument can create a new instance of an object if that object’s class has implemented the interface.

For example: setOnclickListenerVariable(new ClickListener) only if the class ClickListener has implemented IOnClickListener. For this example, Tim uses option 2 to create a new instance of the ClickListener class, a class which was created on the fly as a local class in the main method in Main.java and implements a nested interface. I discuss local classes and nested interfaces below.

public void onClick(){
this.onClickListenerVariable.onClick(this.title);
}

The purpose of this method is to make our onClickListenerVariable object (which we listed as an attribute when we created the Button class above) do something. However, we don’t yet have this object, so if we run this method right after creating a Button object, we will get an error. In order to create a suitable object that the onClickListenerVariable points to (or equals), we need to create a class that will implement the IOnClickListener interface (because we listed IOnClickListener onClickListenerVariable in our attributes). Once we have a class which implements the IOnClickListener interface, we can then create our object.

Here’s where we can use a local class to create an object suitable for the onClickListenerVariable. In the main method Tim creates a local class called “ClickListener”. When we use local classes we skip creating extra files. Notice that ClickListener class implements the IOnClickListener interface in a way we have not yet seen. ClickListener implements Button.IOnClickListener. This new way of implementing an interface uses a nested interface, which in our case, is an interface nested within the Button class.

public interface IOnClickListener {
public void onClick(String title);
}
}//close Button class

This is a nested interface within the Button class. Normally when we implement an interface, we say something like “Class Kangaroo implements IMammal”. Because IOnClickListener is nested in the Button class, we cannot implement the normal way. We implement IOnClickListener interface by saying “Class MyListener implements Button.IOnClickListener”. Tim’s code to implement is “class ClickListener implements Button.OnClickListener{}” and then he appropriately overrides the methods associated with the interface.

Main Class:

import java.util.Scanner;

public class Main {
private static Scanner scanner = new Scanner(System.in);
private static Button btnPrint = new Button("Print");

We have created a Button object named “btnPrint” and assigned it a title of “Print”, but we don’t have anything assigned to btnPrint’s “private IOnClickListener onClickListenerVariable”. Because IOnClickListener is an interface, OnClickListenerVariable will only be allowed to point to (or equal) objects from classes that have implemented IOnClickListener.

public static void main(String[] args) {

class ClickListener implements Button.IOnClickListener {
public ClickListener(){
System.out.println("New instance of ClickListener created");
}

@Override
public void onClick(String title) {
System.out.println(title + " was clicked");
}
}

This is the class that was created in order to create our OnClickListenerVariable object. Now that we have this class available to use, we can use it to create our object. We are about to have values assigned to both “title” and “OnClickListenerVariable” in our Button class object called “btnPrint”.

btnPrint.setOnClickListenerVariable(new ClickListener());

Now we can assign an object to btnPrint using setOnClickListenerVariable. However, the “new ClickListener()” is a bit weird to us newbies, especially since setOnClickListenerVariable took the interface IOnClickListener as a parameter. But here’s the cool thing about using an interface as a parameter. You can stick an object in as the parameter as long as that object’s class has implemented the parameter interface. Because ClickListener class has implemented Button.IOnClickListener, setOnClickListenerVariable() will accept ClickListener objects. We also learned in earlier videos that we can pass “new Class()” as an argument.

btnPrint.onClick();
listen();
}

private static void listen(){
boolean quit = false;
while(!quit){
int choice = scanner.nextInt();
scanner.nextLine();
switch(choice){
case 0:
quit = true;
break;
case 1:
btnPrint.onClick();
}
}
}
}

The rest of the code is pretty clear to me. Once you understand interfaces as parameters and how to use nested interfaces, the Button tutorial is easier to follow. I’m still having a hard time understanding the advantages of doing something like this, but I’ve been pretty happy with the course overall and I trust that I’ll get the answers soon as I progress through the course.

以上這段是同學的解說

因為這次講解牽扯的題目太多

講解又太短,聽完真的頭都痛了

有了這段補充說明幫助甚多

--

--